Javascript 之《如何自動執行生成器》

Hisheng發表於2019-01-29

在 es6 中,引入了一個新的特性:生成器( generator ),用於控制函式執行流程,可實現對函式執行的中斷與恢復。

生成器基本概念

一個簡單的生成器物件定義如下:

function* Gen(){
  const name = `Hisheng`;
  yield `hello, ${name}`;
  console.log(`bye`);
}
複製程式碼

那麼如何執行它呢?直接 Gen() 是不行的,我們必須為它例項化一個迭代器。
你只需要記住:生成器的例項化結果是一個迭代器,或者迭代器是由生成器例項化而來。

const g = Gen();
g.next().value; // hello, Hisheng
g.next(); // bye
複製程式碼

通過依次呼叫 g.next() 來依次執行函式中斷點。

深入

那問題來了,要執行生成器,每次都要例項化並且手動呼叫 g.next() 多麻煩啊,能不能實現自動執行生成器呢?答案是肯定的。

我們先定義一個可以無限次迭代的生成器,例如典型的斐波那契函式:

function* fibonacci(){
  let x = 0, y = 1, z = 0;
  yield 1;
  while(true && z < 200){
    z = x+y;
    yield z;
    x = y;
    y = z;
  }
}
複製程式碼

因為僅僅是用於演示,我們在生成器內部限制了其呼叫次數,不讓它可無限呼叫,儘管理論上定義一個無限執行的生成器是沒有問題的。

同時,我寫了一個可用於自動執行生成器的方法,主要是結合了強大的 Promise,具體實現見函式註釋:

function runGenerator(gen){
  // 先判斷是否是生成器物件
  if(typeof gen !== `function`){
    throw new TypeError(`Generator must be a function`);
  }
  const g = gen();
  if(!g.next){
    throw new TypeError(`function is not a Generator`);
  }
  const next = g.next();

  return new Promise((resolve, reject) => {
    handler(next).then(resolve).catch(reject);
  });

  function handler(next){
    return new Promise((resolve, reject) => {
      try {
        if(next.done){
          resolve();
        }else {
          const nnext = g.next();
          // 這一句只是為了列印出值來驗證生成器是否有被執行,實際中可去掉
          console.log(nnext.value);
          // 遞迴呼叫 handler
          handler(nnext).then(resolve).catch(reject);
        }
      }catch (err){ // catch 生成器中可能丟擲的異常
        reject(err);
      }
    });
  }
}
複製程式碼

執行下看看

runGenerator(fibonacci); // 結果列印出了 1, 1, 2, 3, 5, 8...233
複製程式碼

ok,至此已經達到我們想要的效果了。

題外

關於生成器的使用,最出名的莫過於 大神 TJ 專門寫的庫:co,實現更加巧妙。

相關文章