03.23 es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)

學習asycn之前,各位要先了解一下promise、Iterator以及Generator(阮一峰大神的鏈接:http://es6.ruanyifeng.com/)。眾所周知,JavaScript語言的執行環境是“單線程”的,那麼異步編程對JavaScript語言來說就顯得尤為重要。以前我們大多數的做法是使用回調函數來實現JavaScript語言的異步編程。回調函數本身沒有問題,但如果出現多個回調函數嵌套,例如:進入某個頁面,需要先登錄,拿到用戶信息之後,調取用戶商品信息,代碼如下:

this.$http.jsonp('/login', (res) => { this.$http.jsonp('/getInfo', (info) => { // do something })
})

假如上面還有更多的請求操作,就會出現多重嵌套。代碼很快就會亂成一團,這種情況就被稱為“回調函數地獄”(callback hell)。

於是,我們提出了Promise,它將回調函數的嵌套,改成了鏈式調用。寫法如下:

var promise = new Promise((resolve, reject) => { this.login(resolve)
})
.then(() => this.getInfo())
.catch(() => { console.log("Error") })

從上面可以看出,Promise的寫法只是回調函數的改進,使用then方法,只是讓異步任務的兩段執行更清楚而已。Promise的最大問題是代碼冗餘,請求任務多時,一堆的then,也使得原來的語義變得很不清楚。此時我們引入了另外一種異步編程的機制:Generator。

Generator 函數是一個普通函數,但是有兩個特徵。一是,function關鍵字與函數名之間有一個星號;二是,函數體內部使用yield表達式,定義不同的內部狀態(yield在英語裡的意思就是“產出”)。一個簡單的例子用來說明它的用法:

es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)

function* helloWorldGenerator() {
yield 'hello';
yield 'world'; return 'ending';
}var hw = helloWorldGenerator();

es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)

上面代碼定義了一個 Generator 函數helloWorldGenerator,它內部有兩個yield表達式(hello和world),即該函數有三個狀態:hello,world 和 return 語句(結束執行)。Generator 函數的調用方法與普通函數一樣,也是在函數名後面加上一對圓括號。不同的是,調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,必須調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)為止。換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法可以恢復執行。上述代碼分步執行如下:

es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)

hw.next()// { value: 'hello', done: false }hw.next()// { value: 'world', done: false }hw.next()// { value: 'ending', done: true }hw.next()// { value: undefined, done: true }

es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)

Generator函數的機制更符合我們理解的異步編程思想。

用戶登錄的例子,我們用Generator來寫,如下:

var gen = function* () {
const f1 = yield this.login()
const f2 = yield this.getInfo()
};

雖然Generator將異步操作表示得很簡潔,但是流程管理卻不方便(即何時執行第一階段、何時執行第二階段)。此時,我們便希望能出現一種能自動執行Generator函數的方法。我們的主角來了:async/await。

ES8引入了async函數,使得異步操作變得更加方便。簡單說來,它就是Generator函數的語法糖。

async function asyncFunc(params) {
const result1 = await this.login()
const result2 = await this.getInfo()
}

是不是更加簡潔易懂呢?

變體

異步函數存在以下四種使用形式:

  • 函數聲明: <code>async function foo() {}/<code>

  • 函數表達式: <code>const foo = async function() {}/<code>

  • 對象的方式: <code>let obj = { async foo() {} }/<code>

  • 箭頭函數: <code>const foo = async () => {}/<code>

常見用法彙總

處理單個異步結果:

async function asyncFunc() {
const result = await otherAsyncFunc();
console.log(result);
}

順序處理多個異步結果:

es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)

async function asyncFunc() {
const result1 = await otherAsyncFunc1();
console.log(result1);
const result2 = await otherAsyncFunc2();
console.log(result2);
}

es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)

並行處理多個異步結果:

es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)

async function asyncFunc() {
const [result1, result2] = await Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2()
]);
console.log(result1, result2);
}

es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)

處理錯誤:

es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)

async function asyncFunc() { try {
await otherAsyncFunc();
} catch (err) {
console.error(err);
}
}

es8之asycn函數(前端必須瞭解,同樣也是一道重要的面試題)


分享到:


相關文章: