1. 程式人生 > >sec-generatoryield,這是對yield的介紹

sec-generatoryield,這是對yield的介紹

  開局官宣:sec-generatoryield,這是對yield的介紹。
  
  同樣巴拉巴拉列了9條,將以上鍊接中的說明簡化成3條:
  
  1. 在GeneratorFunction內,當遇到yield關鍵字的時候,先將執行上下文設定為yield之後的表示式進行執行,並且將該表示式返回值作為當前迭代的結果;
  
  2. 將gen上下文從上下文堆疊中移除,將上級(gen之外)上下文(依次)恢復為當前執行的上下文,此過程中需設定gen上下文的評估狀態,以便在上下文恢復時(下一次呼叫.next)繼續操作迭代;
  
  3. 通過.next方法依次執行迭代器。
  
  先對上面3點有點印象,再來看看 Generator。
  
  Generator 物件是通過 GeneratorFunction 執行返回的物件,具有可迭代的特性(迭代器協議定義了一種標準的方式來產生一個有限或無限序列的值),關於迭代器詳見"迭代器"。
  
  複製程式碼
  
  GeneratorFunction => Generator
  
  GeneratorFunction.prototype
  
  next 返回迭代結果
  
  return 傳入引數作為迭代結果的value並返回該迭代項,並且結束Generator物件的迭代
  
  throw 丟擲錯誤值,並且結束Generator物件的迭代
  
  複製程式碼
  
  每個迭代結果都包含 done 和 value :
  
  1. done 表示生成器是否被完成;
  
  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(www.mhylpt.com/ ),還是進入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 www.dfgjyl.cn ,done:false},列印 2;
  
  3. 在step內,result={value:3,done:www.dfgjpt.com false},列印 3;
  
  ...
  
  10. 在step內,result={value:www.michenggw.com 10,done:false},列印 10;
  
  11. 在step內,result={value:www.gcyL157.com undefined,done:true},迭代物件被完成。
  
  如果yield後跟的是非同步表示式呢?
  
  程式碼如下:
  
  複製程式碼
  
  // 基於上面的run函式
  
  run(function*(www.furong157.com){
  
  const value1=yield fn1(www.mingcheng178.com);
  
  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);
  
  }
  
  });
  
  };
  
  複製程式碼
  
  可能有些關聯的文章:
  
  理解 async/await 的執行
  
  分步理解 Promise 的實現
  
  Generator關係圖
  
  co
  
  文章僅供參考!!!關於更多Generator知識,以閱讀文章開頭官方文件為準,如更多的術語以及它們各代表什麼過程...
  
  學習過程中,多寫幾次總是會記得深刻些。