generator函式與async/await
理解async函式就要先理解generator函式,因為async就是Generator函式的語法糖
Generator 函式
Generator 函式是 ES6 提供的一種非同步程式設計解決方案,可以先理解為一個狀態機,封裝了多個內部狀態,執行Generator函式返回一個遍歷器物件,通過遍歷器物件,可以依次遍歷 Generator 函式內部的每一個狀態
語法上,Generator 函式是一個普通函式,但是有兩個特徵。
一是,function關鍵字與函式名之間有一個星號;
二是,函式體內部使用yield表示式,定義不同的內部狀態(yield在英語裡的意思就是“產出”);
function* helloGenerator() { yield 'hello' yield 'Generator' return 'ending' } let Generator = helloGenerator()
呼叫Generator函式後並不執行,返回的也不是函式執行結果而是一個指向內部狀態的指標物件,也就是遍歷器物件(Iterator Object)。
必須呼叫遍歷器物件的next方法,使得指標移向下一個狀態。
console.log(Generator.next()) // {value: 'hello', done: false}
console.log(Generator.next()) // {value: 'Generator', done: false}
console.log(Generator.next()) // {value: 'ending', done: true}
第一次呼叫next方法,Generator函式開始執行,直到遇到yield表示式為止。next方法返回一個物件,value屬性就是當前yield表示式的值hello,done屬性的值false,表示遍歷還沒有結束。
第二次呼叫next方法,Generator 函式從上次yield表示式停下的地方,一直執行到下一個yield表示式
繼續呼叫next方法直到done屬性值為true或者執行到return語句(如果沒有return語句就執行到函式結束),表示遍歷已經結束
如果再次呼叫next方法,此時Generator函式已經執行完畢,next方法返回物件的value屬性為undefined,done屬性為true。以後再呼叫next方法,返回的都是這個值
yield 表示式
可以理解為暫停的標誌,遇到yield表示式,就暫停執行後面的操作,並將緊跟在yield後面的那個表示式的值,作為返回的物件的value屬性值。yield表示式與return語句都能返回緊跟在語句後面的那個表示式的值。區別在於每次遇到yield,函式暫停執行,下一次再從該位置繼續向後執行,而return語句不具備位置記憶的功能。一個函式裡面,只能執行一次return語句,但是可以執行多次yield表示式。從另一個角度看,也可以說 Generator 生成了一系列的值,這也就是它的名稱的來歷(英語中,generator 這個詞是“生成器”的意思)。另外需要注意,yield表示式只能用在 Generator 函式裡面,用在其他地方都會報錯。
next方法的引數
next方法可以帶一個引數,該引數就會被當作上一個yield表示式的返回值
function* foo(x) {
let y = yield x + 1
let k = yield y + 2
yield k / 2
return k
}
let a = foo(1)
console.log(a.next()) // {value: 2, done: false}
console.log(a.next(3)) // {value: 5, done: false}
console.log(a.next(8)) // {value: 4, done: false}
console.log(a.next()) // {value: 8, done: true}
第一次執行next方法時,返回1+1的值2;第二次呼叫next方法,將上一次yield表示式的值設為3,y等於3,返回y + 2的值5;第三次呼叫next方法,將上一次yield表示式的值設為8,k等於8,返回k/2的值4
注意,由於next方法的引數表示上一個yield表示式的返回值,所以在第一次使用next方法時,傳遞引數是無效的。
next()、throw()、return()
除了next方法還有throw()、return()兩個方法,這三個方法本質上是同一件事,可以放在一起理解。它們的作用都是讓 Generator 函式恢復執行,並且使用不同的語句替換yield表示式。
next()是將yield表示式替換成一個值。
throw()是將yield表示式替換成一個throw語句。
const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.throw(new Error('出錯了')); // Uncaught Error: 出錯了
// 相當於將 let result = yield x + y 替換成 let result = throw(new Error('出錯了'));
return()是將yield表示式替換成一個return語句。
gen.return(2); // {value: 2, done: true}
// 相當於將 let result = yield x + y替換成 let result = return 2;
yield* 表示式
ES6提供了yield*表示式,用來在一個Generator函式裡面執行另一個Generator函式。
function* foo(x) {
yield 1
yield* bar()
yield 4
}
function* bar() {
yield 2
yield 3
}
let a = foo()
console.log(a.next()) // {value: 1, done: false}
console.log(a.next()) // {value: 2, done: false}
console.log(a.next()) // {value: 3, done: false}
console.log(a.next()) // {value: 4, done: false}
console.log(a.next()) // {value: undefined, done: true}
由於yield* bar()語句得到的值,是一個遍歷器,所以要用星號表示。執行結果就是使用一個遍歷器,遍歷了多個Generator函式,有遞迴的效果。
yield*後面的 Generator 函式(沒有return語句時),等同於在 Generator 函式內部,部署一個for...of迴圈。
async/await
ES7 中引入了 async/await,async 是一個通過非同步執行並隱式返回 Promise 作為結果的函式。async 函式的實現原理,就是將 Generator函式和自動執行器,包裝在一個函式裡。
根據阮一峰老師的介紹,async函式就是Generator函式的語法糖,並對Generator函式進行了改進。
上面程式碼async函式就是將Generator函式的星號(*)替換成async,將yield替換成await,僅此而已
async函式對 Generator 函式的改進,體現在以下四點
- 內建執行器
Generator 函式的執行必須靠執行器,需要呼叫next方法,才能真正執行,得到最後結果。 - 更好的語義
async和await,比起星號和yield,語義更加清楚。async表示函式裡有非同步操作,await表示緊跟在後面的表示式需要等待結果。 - 更廣的適用性
co模組約定,yield命令後面只能是 Thunk 函式或 Promise 物件,而async函式的await命令後面,可以是 Promise 物件和原始型別的值(數值、字串和布林值,但這時會自動轉成立即 resolved 的 Promise 物件) - 返回值是promise
async函式的返回值是Promise物件,比Generator函式的返回值是Iterator物件方便多了。可以用then方法指定下一步的操作。
async函式完全可以看作多個非同步操作,包裝成的一個 Promise 物件,而await命令就是內部then命令的語法糖。