ES6新引入了Generator函數,可以通過yield關鍵字,把函數的執行流挂起,為改變執行流程提供了可能,進而為異步程式設計提供解決方案。
Generator函數有多種了解角度。文法上,首先可以了解成,Generator函數是一個狀态機,封裝了多個内部狀态;執行Generator函數會傳回一個周遊器對象,也就是說,Generator函數除了是狀态機,還是一個周遊器對象生成函數,傳回的周遊器對象,可以依次周遊Generator函數内部的每一個狀态。形式上,Generator函數是一個普通的函數,但是有兩個特征:function關鍵字和函數名之間有一個星号,星号用來表示函數為Generator函數;函數體内部使用yield語句,yield語句用來定義函數不同的内部狀态。
ES6沒有規定,function關鍵字與函數名之間的星号,寫在哪個位置。下面的寫法都能通過,但一般是第三種。function * foo(x, y) { ··· } function *foo(x, y) { ··· } function* foo(x, y) { ··· } function*foo(x, y) { ··· }
如果Generator 函數是一個對象的屬性,可以簡寫成下面的形式。它的完整形式如下:let obj = { * myGeneratorMethod() {} };
let obj = { myGeneratorMethod: function* () {} };
Generator函數的調用方法與普通函數一樣,也是在函數名後面加上一對圓括号。不同的是,調用Generator函數後,該函數并不執行,傳回的也不是函數的運作結果,而是一個指向内部狀态的指針對象(是一個周遊器對象)。
下一步,必須調用周遊器對象的next方法,使得指針移向下一個狀态,也就是說,每次調用next方法,内部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式或return表達式。換言之,Generator函數是分段執行的,yield表達式是暫停執行的标記,而next方法可以恢複執行。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next();// { value: 'hello', done: false }
hw.next();// { value: 'world', done: false }
hw.next();// { value: 'ending', done: true }
hw.next();// { value: undefined, done: true }
上面代碼一共調用了四次next方法。
第一次調用,Generator函數開始執行,直到遇到第一個yield表達式為止。next方法傳回一個對象,它的value屬性就是目前yield表達式的值hello,done屬性的值false,表示周遊還沒有結束。
第二次調用,Generator函數從上次yield表達式停下的地方,一直執行到下一個yield表達式。next方法傳回的對象的value屬性就是目前yield表達式的值world,done屬性的值false,表示周遊還沒有結束。
第三次調用,Generator函數從上次yield表達式停下的地方,一直執行到return語句(如果沒有return語句,就執行到函數結束)。next方法傳回的對象的value屬性,就是緊跟在return語句後面的表達式的值(如果沒有return語句,則value屬性的值為undefined),done屬性的值為true,表示周遊已經結束。
第四次調用,此時Generator函數已經運作完畢,next方法傳回的對象的value屬性為undefined,done屬性為true。以後再調用next方法,傳回的都是這個值。
總結一下,調用Generator函數,傳回一個周遊器對象,代表Generator函數的内部指針。以後,每次調用周遊器對象的next方法,就會傳回一個有着value和done兩個屬性的對象,value屬性表示目前的内部狀态的值,是yield表達式後面那個表達式的值,done屬性是一個布爾值,表示是否周遊結束。
yield表達式:
由于Generator函數傳回的周遊器對象,隻有調用next方法才會周遊下一個内部狀态,是以其實提供了一種可以暫停執行的函數。yield表達式就是暫停标志。
yield表達式後面的表達式,隻有當調用next方法、内部指針指向該語句時才會執行,是以等于為JavaScript提供了手動的惰性求值的文法功能。
yield*表達式:
如果在Generator函數内部,調用另一個Generator函數,那麼需要在前者的函數内部,自己手動完成周遊。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
// 手動周遊 foo()
for (let i of foo()) {
console.log(i);
}
yield 'y';
}
for (let v of bar()){
console.log(v);// x、a、b、y
}
上面代碼中,foo和bar都是Generator函數,在bar裡面調用foo,就需要手動周遊foo。如果有多個Generator函數嵌套,寫起來就非常麻煩。
ES6提供了yield*表達式,用來在一個Generator函數裡面執行另一個Generator函數。
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
for (let v of bar()){
console.log(v);// x、a、b、y
}
方法:
- next()方法:每次調用next方法,内部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式或者return語句。會傳回一個有着value和done兩個屬性的對象,value屬性表示目前的内部狀态的值,是yield表達式後面那個表達式的值,done屬性是一個布爾值,表示是否周遊結束。
function* func1(){ yield '1'; yield '2'; } let f = func1(); console.log(f.next()); console.log(f.next()); console.log(f.next());
yield表達式本身是沒有傳回值的,或者說總是傳回undefined。next方法可以帶一個參數,該參數會被當做上一個yield表達式的傳回值。《十五》ES6中的Generator函數 function* func1(){ var x = yield '1'; console.log(x); var y = yield '2'; console.log(y); } let f = func1(); console.log(f.next()); console.log(f.next()); console.log(f.next(5));
《十五》ES6中的Generator函數 - return()方法:return 方法提供參數時,傳回該參數并結束周遊 Generator 函數;不提供參數時,傳回 undefined并結束周遊 Generator 函數 。
function* func1(){ yield '1'; yield '2'; } let f = func1(); console.log(f.return()); console.log(f.return());
《十五》ES6中的Generator函數 function* func1(){ yield '1'; yield '2'; } let f = func1(); console.log(f.return('哈哈')); console.log(f.return());
《十五》ES6中的Generator函數 - throw()方法:throw方法可以在Generator函數體外抛出異常,然後在Generator函數體内捕獲。throw()方法可以接受一個參數,這個參數會被catch語句接收。
function* func1(){ try{ yield; }catch(e){ console.log(`Generator函數内部捕獲:${e}`) } } let f = func1(); f.next(); try{ f.throw('a'); f.throw('b'); }catch(e){ console.log(`Generator函數外部捕獲:${e}`) }
周遊器對象f連續在體外抛出兩個錯誤。第一個錯誤可以被Generator函數體内的catch語句捕獲;由于Generator函數内部的catch語句已經被執行過了,是以第二個錯誤隻能被函數體外的catch語句捕獲。《十五》ES6中的Generator函數
使用for…of循環周遊Generator函數:
for…of循環可以自動周遊Generator函數運作時生成的Iterator對象,此時不再需要調用next方法。
function* foo() {
yield 1;
yield 2;
yield 3;
return 4;
}
for (let v of foo()) {
console.log(v);
}// 1 2 3
上面代碼使用for…of循環,依次顯示3個yield表達式的值。一旦next方法傳回的對象的done屬性為true,for…of循環就會中止,是以上面代碼的return語句傳回的4,不包括在for…of循環之中。
除了for…of循環以外,擴充運算符、解構指派和Array.from方法内部調用的,都是周遊器接口,這意味着,它們都可以将Generator函數傳回的Iterator對象,作為參數。
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 擴充運算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解構指派
let [x, y] = numbers();
x // 1
y // 2
// for...of 循環
for (let n of numbers()) {
console.log(n)
}// 1、2
Generator函數的異步應用:
// 建立axion請求
function query(url) {
axios.get(url).then(res => {
//每次異步任務成功之後,才去執行Generator函數的下一步
stepsFunc.next(res.data.data)
});
}
// 建立生成器函數
function* steps() {
const sheepNumbers = yield query('http://cg.entrobus.com/api-service/goat/queryAdoptNumber');
console.log(`第一次接口調用成功:${JSON.stringify(sheepNumbers)}`)
const sheepInfo = yield query(`http://cg.entrobus.com/api-service/goat/queryGoatInfoByNumber?number=${sheepNumbers.numbers[0]}`);
console.log(`第二次接口調用成功:${JSON.stringify(sheepInfo)}`)
}
const stepsFunc = steps();
stepsFunc.next(); // 流程開啟