在 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,實現更加巧妙。