上三篇分別介紹了Promise和Generator,包括用Thunk自動(dòng)執(zhí)行Generator,本篇介紹用co模塊將Generator和Promise結(jié)合起來,自動(dòng)執(zhí)行Generator。co模塊參照這里,以前co模塊返回的是Thunk,現(xiàn)在返回的是promise。安裝命令:npm install co
你可以從Github上獲取本篇例子代碼。仍舊以讀取文件為例,先用Promise來執(zhí)行異步操作:
var fs = require('fs');
var readFile = function (fileName, options) {
return new Promise(function (resolve, reject){
fs.readFile(fileName, options, function(error, data){
if (error) return reject(error);
resolve(data);
});
});
};
readFile('./apples.txt', 'utf8').then(function(data){
console.log(data);
return readFile('./oranges.txt', 'utf8');
}).then(function(data){
console.log(data);
}).catch(function(error){
console.log(error);
});
先將異步函數(shù)fs.readFile封裝成一個(gè)Promise對象,起名readFile。然后調(diào)用then依次執(zhí)行異步操作。
但看起來一堆then,代碼不夠清晰,改成用Generator來定義異步操作的步驟:
var gen = function* () {
var f1 = yield readFile('./apples.txt', 'utf8');
console.log(f1);
var f2 = yield readFile('./oranges.txt', 'utf8');
console.log(f2);
};
var g = gen();
g.next().value.then(function(data) {
g.next(data).value.then(function(data) {
g.next(data);
});
});
有了上兩篇的基礎(chǔ),這些代碼應(yīng)該很容易就能看懂了。用Generator定義異步步驟,確實(shí)比Promise的then清晰很多,維護(hù)方便。但Generator的問題是,一步步執(zhí)行next很麻煩。我們需要讓Generator自動(dòng)執(zhí)行起來:
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
run(gen);
這些都是之前三篇介紹過的,炒炒冷飯。co源代碼就是上面run方法的擴(kuò)展, 因?yàn)樵创a非常短,干脆直接貼出來看看它是怎么實(shí)現(xiàn)的:(為縮減篇幅,刪除了源代碼里的注釋)
function co(gen) { //co函數(shù)接受Generator函數(shù)作參數(shù),最終返回一個(gè) Promise 對象
var ctx = this;
var args = slice.call(arguments, 1);
return new Promise(function(resolve, reject) { //co函數(shù)返回的是一個(gè)Promise對象
//先檢查參數(shù),如果是Generator函數(shù)就獲得一個(gè)遍歷器對象。
//如果不是,或沒有next方法,就將其轉(zhuǎn)成一個(gè)狀態(tài)為Resolved的Promise對象,并返回。
if (typeof gen === 'function') gen = gen.apply(ctx, args);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled(); //Generator函數(shù)的next方法被封裝在此處
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret); //關(guān)鍵在這里,詳見下面next方法
return null;
}
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
//該函數(shù)會無限遞歸,直到Generator函數(shù)執(zhí)行完畢為止
function next(ret) {
//返回值的done屬性為true表示Generator函數(shù)遍歷完畢,返回Promise對象
if (ret.done) return resolve(ret.value);
//源碼中定義了toPromise方法可以將Object,Array,Thunk,Generator轉(zhuǎn)成Promise對象。
//即你需要確保Generator函數(shù)的yield命令后面,是這些對象
var value = toPromise.call(ctx, ret.value);
//步步都確保已經(jīng)是Promise對象后,執(zhí)行then方法,通過onFulfilled再次調(diào)用該函數(shù)
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
//參數(shù)有誤的話,捕捉異常,中止處理
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}
上面co源代碼加上注釋后應(yīng)該可以理解。用co模塊自動(dòng)執(zhí)行Generator函數(shù)只需一行代碼:co(gen);。是不是簡單到令人發(fā)指?完整代碼如下:
var fs = require('fs');
var co = require('co');
var readFile = function (fileName, options) {
return new Promise(function (resolve, reject){
fs.readFile(fileName, options, function(error, data){
if (error) return reject(error);
resolve(data);
});
});
};
var gen = function* () {
var f1 = yield readFile('./apples.txt', 'utf8');
console.log(f1);
var f2 = yield readFile('./oranges.txt', 'utf8');
console.log(f2);
};
co(gen);
先定義一個(gè)Promise對象將異步函數(shù)封裝起來,用Generator函數(shù)定義異步操作流程,co模塊里將yield命令后的異步操作包裝成一個(gè)Promise對象,用then交回執(zhí)行權(quán)來遞歸自動(dòng)執(zhí)行Generator函數(shù)。