Generator & yieldhtml
開局官宣:sec-generatoryield,這是對yield的介紹。git
一樣巴拉巴拉列了9條,將以上連接中的說明簡化成3條:github
1. 在GeneratorFunction內,當遇到yield關鍵字的時候,先將執行上下文設置爲yield以後的表達式進行執行,而且將該表達式返回值做爲當前迭代的結果;promise
2. 將gen上下文從上下文堆棧中移除,將上級(gen以外)上下文(依次)恢復爲當前執行的上下文,此過程當中需設置gen上下文的評估狀態,以便在上下文恢復時(下一次調用.next)繼續操做迭代;app
3. 經過.next方法依次執行迭代器。異步
先對上面3點有點印象,再來看看 Generator。async
Generator 對象是經過 GeneratorFunction 執行返回的對象,具備可迭代的特性(迭代器協議定義了一種標準的方式來產生一個有限或無限序列的值),關於迭代器詳見"迭代器"。函數
GeneratorFunction => Generator GeneratorFunction.prototype next 返回迭代結果 return 傳入參數做爲迭代結果的value並返回該迭代項,而且結束Generator對象的迭代 throw 拋出錯誤值,而且結束Generator對象的迭代
每一個迭代結果都包含 done 和 value :學習
1. done 表示生成器是否被完成;this
2. value 表示當前的值。
來個例子:
function* iterator1(){ console.log(1); yield '1'; console.log(2) yield *iterator2(); yield '2'; console.log(3); } function* iterator2(){ yield '3'; console.log(4); yield '4'; } function fn1(){ console.log(5) fn2(); console.log(6) } function fn2(){ console.log(7) var iter = iterator1(); console.log(iter.next()); console.log(iter.next()); console.log(iter.next()); console.log(8); console.log(iter.next()); console.log(iter.next()); console.log(9); } fn1(); /* * 輸出順序 * var iter = iterator1(); // before : 5 7 * console.log(iter.next()); // 1 {value:1,done:false} * console.log(iter.next()); // 2 {value:3,done:false} * console.log(iter.next()); // 4 {value:4,done:false} * console.log(8); // 8 * console.log(iter.next()); // {value:2,done:false} * console.log(iter.next()); // 3 {value:undefined,done:true} * console.log(9); // 9 after : 6 */
看輸出順序(多個Generator嵌套可看做爲在外部Generator的某個索引位置插入內部Generator的元素做爲迭代項):
1. fn1被執行,首先輸出 5;
2. 進入fn2,輸出 7;
3. fn2中生成iter,並首次調用iter.next(),執行了iterator1裏面第一個yield以前的console,輸出 1,而後輸出 {value: "1", done: false};
4. 調用第二個iter.next(),進入iterator2中,輸出 2,而後輸出 {value:'3',done:false};
5. 調用第三個iter.next(),仍是進入iterator2,輸出 4,而後輸出 {value:'4',done:false};
6. 調用fn2中的console.log(8),輸出 8;
7. 調用第四個iter.next(),這時候iterator2裏面執行完了,繼續執行iterator1的後續代碼,輸出 {value:2,done:false};
8. 調用第五個iter.next(),繼續iterator1的後續代碼,輸出 3,這時候iterator1的迭代結束,輸出 {value:undefined,done:true};
9. 調用fn2中的console.log(9),輸出 9;
10. 調用fn1中的console.log(6),輸出 6。
Generator的任務執行器
Generator經過.next方法來依次作迭代的執行,然而每次都須要手動寫方法調用是個問題。而後便有了迭代任務的執行器,在執行器內將主動調用.next以執行迭代。
以下面例子:
function run(gen){ const task = gen(); // 定義一個對象用於存每一個迭代結果,傳入result.value 賦值到指定對象上 let result = task.next(); // 若是迭代未結束,則繼續執行next(),獲取下個迭代結果,以此類推... function step(){ if(!result.done){ result = task.next(result.value); step(); } } step(); } run(function*(){ let i = 0; while(i<10) { yield ++i, console.log(i); } }); // 1 2 3 4 5 6 7 8 9 10
在run(function*(/* ... */))中,先執行GeneratorFunction迭代對象返回Generator,而後用一個變量來存每次迭代結果...執行過程以下:
1. result={value:1,done:false},打印 1;
2. 在step內,result={value:2,done:false},打印 2;
3. 在step內,result={value:3,done:false},打印 3;
...
10. 在step內,result={value:10,done:false},打印 10;
11. 在step內,result={value:undefined,done:true},迭代對象被完成。
若是yield後跟的是異步表達式呢?
代碼以下:
// 基於上面的run函數 run(function*(){ const value1=yield fn1(); console.log('v1',value1); const value2 = yield fn2(); console.log('v2',value2) }) function fn1(){ const promise = new Promise(resolve => setTimeout(()=> resolve(' success'),3000)); promise.then(res=> console.log(res) ) return promise; }; function fn2(){ console.log('fn2'); return 'fn2'; } // v1 Promise // fn2 // v2 fn2 // 3s 後 success
假如需求須要fn2的執行依賴fn1的異步返回值,簡單改造一下run執行器試試:
// 修改上面的run函數 function run(gen){ const iter = gen(); // result用來存儲每一次迭代結果 let result = iter.next(); step(); function step(){ // 若是迭代對象未完成 if(!result.done){ // 若是是Promise,則在.then以後執行next if(result.value instanceof Promise){ result.value.then(res =>{ result = iter.next(res); step(); }) }else{ result = iter.next(result.value); step(); } } } }
以上是沒看co代碼以前針對問題"若是Generator對象迭代過程當中某個迭代處理依賴上一個迭代結果該怎麼辦"想到的方法... 在實現方式上是差了些,但也能夠用...
co實現的更好看且適用,每次迭代(function, promise, generator, array, object)都包裝成Promise處理,針對不一樣場景/類型則關注toPromise層的判斷。
對比一下ES7 async/await經過tsc --target es5 後的代碼。
1. 首先是個__awaiter方法,裏面是 new Promise;
2. 而後是個__generator方法,裏面是GeneratorFunction。
也是用Promise包裝Generator的模式實現... 把__awaiter摘出來後的代碼:
var run = function (thisArg,_arguments,generator) { return new Promise(function (resolve, reject) { generator = generator.apply(thisArg, _arguments || []) function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } step(generator.next()); function step(result) { result.done ? resolve(result.value) : new Promise(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } }); };
可能有些關聯的文章:
文章僅供參考!!!關於更多Generator知識,以閱讀文章開頭官方文檔爲準,如更多的術語以及它們各表明什麼過程...
學習過程當中,多寫幾回老是會記得深入些。