ES6中Generator
Generator是ES6一個很有意思的特性,也是不容易理解的特性。不同於let/const
提供了塊級作用域這樣明顯的目的,這玩意兒被搞出來到底是幹嘛的?
首先我們需要明確一個問題,在JavaScript中,任何一個函數只要開始執行,便無法停止下來直到執行完成(別跟我提debug,你見過哪個用戶在使用產品的還得開個debug調試你的代碼)。
but,Generator提供這種能力。 看下面代碼:
function *g(){
console.log('start');
yield 1;
console.log('middle');
yield 2;
console.log('end');
}
var g1 = g();
console.log(g1.next());
// start
// {value: 1, done: false}
console.log(g1.next());
// middle
// {value: 2, done: false}
console.log(g1.next());
// end
// {value: undefined, done: true}
根據輸出結果,我們看到,在函數g
中,碰到yield
關鍵詞,運行的程序會停下來。只有調用 next()
方法,纔會繼續執行函數g
中的代碼。所以函數g
本身有暫停狀態。
至此,我們需要知道:
- Generator不是函數,不是函數,不是函數;
- g()不會立即出發執行,而是一上來就暫停,並返回一個Iterator對象;
- 每次g1.next()都會打破暫停狀態去執行,直到遇到下一個yield或者return
- 遇到yield時,會執行yeild後面的表達式,並返回執行之後的值,然後再次進入暫停狀態,此時done: false。
- 遇到return時,會返回值,執行結束,即done: true
- 每次g.next()的返回值永遠都是{value: ... , done: ...}的形式
Generator與異步
既然Generator可以函數停下來,有些腦洞清奇的人,想到了可不可以用Generator處理異步程序。
先看一個傳統例子:
function asyn(fn) {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
fn();
resolve(true);
}, 1000);
});
}
function main() {
console.log('start');
asyn(function(d) {
console.log('async one');
asyn(function(d) {
console.log('async two');
console.log('end');
});
});
}
main();
再來看看使用了Generator的異步程序:
function asyn(fn) {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
fn();
resolve(true);
}, 1000);
});
}
co(function*() {
console.log('start');
yield asyn(function(d) {
console.log('async one');
});
yield asyn(function(d) {
console.log('async two');
});
console.log('end');
});
function co(fn) {
return new Promise((resolve,reject)=>{
let g = fn();
onFullfilled();
function onFullfilled() {
let ret = null;
ret = g.next();
next(ret);
}
function next(ret) {
if(ret.done) return resolve(ret.value);
ret.value.then(onFullfilled);
}
} );
}
函數在異步程序沒有采用嵌套回調,是直接用同步的方式寫了出來。道理大概就是,有兩個異步程序,用小明和小紅指代它們。小紅需要等小明執行完了纔可以被執行,那麼我們在執行到小明時,暫停程序的運行(通過yield
),等到小明有了返回結果時,再執行後面跟着的小紅(next()
)。
在上面的程序中,我們添加了一個co
函數,這個函數的作用是讓Generator自動執行下去。直白來說,就是當第一個異步函數返回後,自動調用next()
方法運行後面的代碼。
Generator與Koa
Koa是基於Node.js的Web應用框架。在Koa中,處理的異步程序主要是網絡請求(HTTP)、文件讀取和數據查詢。這裏面的異步場景較多,如果再加上程序分層,採用傳統的callback方式,那回調多了去了。
app.on('get', function(){
auth(function(){
router(function(){
find(function(){
save(function(){
render(function(){
//......
})
})
})
})
})
})
這樣寫法對於程序維護及其不利,毫無便捷性可言。在有了generator後,我們便可以像上面那種方式來寫程序。Koa最初的版本就是通過這種方式,讓中間處理程序都轉成一個個"yield"(中間件)。通過中間件的形式去處理客戶端請求,讓開發App應用更加靈活,不受框架自身限制。
在最新的Koa2中,已經拋棄了Genetator,轉而使用async/await
。
但是無論採用哪種方式,其本質都是利用了Promise。