進(jìn)入正文之前,先插播一條消息。 我七年前翻譯的《軟件隨想錄》再版了(京東鏈接)。這次是《Joel論軟件》兩卷同時(shí)再版,第一卷是新譯本,第二卷是我翻譯的。 
本書(shū)的作者是著名程序員、StackOverflow的創(chuàng)始人 Joel Splosky。我覺(jué)得,它是軟件項(xiàng)目管理的最好讀物之一,推薦閱讀。 ======================================== 以下是《深入掌握 ECMAScript 6 異步編程》系列文章的第三篇。 一、什么是 co 函數(shù)庫(kù)?co 函數(shù)庫(kù)是著名程序員 TJ Holowaychuk 于2013年6月發(fā)布的一個(gè)小工具,用于 Generator 函數(shù)的自動(dòng)執(zhí)行。 
比如,有一個(gè) Generator 函數(shù),用于依次讀取兩個(gè)文件。 var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
co 函數(shù)庫(kù)可以讓你不用編寫(xiě) Generator 函數(shù)的執(zhí)行器。 var co = require('co');
co(gen);
上面代碼中,Generator 函數(shù)只要傳入 co 函數(shù),就會(huì)自動(dòng)執(zhí)行。 co 函數(shù)返回一個(gè) Promise 對(duì)象,因此可以用 then 方法添加回調(diào)函數(shù)。 co(gen).then(function (){
console.log('Generator 函數(shù)執(zhí)行完成');
})
上面代碼中,等到 Generator 函數(shù)執(zhí)行結(jié)束,就會(huì)輸出一行提示。 二、 co 函數(shù)庫(kù)的原理為什么 co 可以自動(dòng)執(zhí)行 Generator 函數(shù)? 前面文章說(shuō)過(guò),Generator 函數(shù)就是一個(gè)異步操作的容器。它的自動(dòng)執(zhí)行需要一種機(jī)制,當(dāng)異步操作有了結(jié)果,能夠自動(dòng)交回執(zhí)行權(quán)。 兩種方法可以做到這一點(diǎn)。 (1)回調(diào)函數(shù)。將異步操作包裝成 Thunk 函數(shù),在回調(diào)函數(shù)里面交回執(zhí)行權(quán)。 (2)Promise 對(duì)象。將異步操作包裝成 Promise 對(duì)象,用 then 方法交回執(zhí)行權(quán)。
co 函數(shù)庫(kù)其實(shí)就是將兩種自動(dòng)執(zhí)行器(Thunk 函數(shù)和 Promise 對(duì)象),包裝成一個(gè)庫(kù)。使用 co 的前提條件是,Generator 函數(shù)的 yield 命令后面,只能是 Thunk 函數(shù)或 Promise 對(duì)象。 上一篇文章已經(jīng)介紹了基于 Thunk 函數(shù)的自動(dòng)執(zhí)行器。下面來(lái)看,基于 Promise 對(duì)象的自動(dòng)執(zhí)行器。這是理解 co 函數(shù)庫(kù)必須的。 三、基于 Promise 對(duì)象的自動(dòng)執(zhí)行還是沿用上面的例子。首先,把 fs 模塊的 readFile 方法包裝成一個(gè) Promise 對(duì)象。 var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
然后,手動(dòng)執(zhí)行上面的 Generator 函數(shù)。 var g = gen();
g.next().value.then(function(data){
g.next(data).value.then(function(data){
g.next(data);
});
})
手動(dòng)執(zhí)行其實(shí)就是用 then 方法,層層添加回調(diào)函數(shù)。理解了這一點(diǎn),就可以寫(xiě)出一個(gè)自動(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);
上面代碼中,只要 Generator 函數(shù)還沒(méi)執(zhí)行到最后一步,next 函數(shù)就調(diào)用自身,以此實(shí)現(xiàn)自動(dòng)執(zhí)行。 四、co 函數(shù)庫(kù)的源碼co 就是上面那個(gè)自動(dòng)執(zhí)行器的擴(kuò)展,它的源碼只有幾十行,非常簡(jiǎn)單。 首先,co 函數(shù)接受 Generator 函數(shù)作為參數(shù),返回一個(gè) Promise 對(duì)象。 function co(gen) {
var ctx = this;
return new Promise(function(resolve, reject) {
});
}
在返回的 Promise 對(duì)象里面,co 先檢查參數(shù) gen 是否為 Generator 函數(shù)。如果是,就執(zhí)行該函數(shù),得到一個(gè)內(nèi)部指針對(duì)象;如果不是就返回,并將 Promise 對(duì)象的狀態(tài)改為 resolved 。 function co(gen) {
var ctx = this;
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.call(ctx);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
});
}
接著,co 將 Generator 函數(shù)的內(nèi)部指針對(duì)象的 next 方法,包裝成 onFulefilled 函數(shù)。這主要是為了能夠捕捉拋出的錯(cuò)誤。 function co(gen) {
var ctx = this;
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.call(ctx);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled();
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
});
}
最后,就是關(guān)鍵的 next 函數(shù),它會(huì)反復(fù)調(diào)用自身。 function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
上面代碼中,next 函數(shù)的內(nèi)部代碼,一共只有四行命令。 第一行,檢查當(dāng)前是否為 Generator 函數(shù)的最后一步,如果是就返回。 第二行,確保每一步的返回值,是 Promise 對(duì)象。 第三行,使用 then 方法,為返回值加上回調(diào)函數(shù),然后通過(guò) onFulfilled 函數(shù)再次調(diào)用 next 函數(shù)。 第四行,在參數(shù)不符合要求的情況下(參數(shù)非 Thunk 函數(shù)和 Promise 對(duì)象),將 Promise 對(duì)象的狀態(tài)改為 rejected,從而終止執(zhí)行。
五、并發(fā)的異步操作co 支持并發(fā)的異步操作,即允許某些操作同時(shí)進(jìn)行,等到它們?nèi)客瓿?,才進(jìn)行下一步。 這時(shí),要把并發(fā)的操作都放在數(shù)組或?qū)ο罄锩妗?/p> // 數(shù)組的寫(xiě)法co(function* () {
var res = yield [
Promise.resolve(1),
Promise.resolve(2)
];
console.log(res);
}).catch(onerror);// 對(duì)象的寫(xiě)法co(function* () {
var res = yield {
1: Promise.resolve(1),
2: Promise.resolve(2),
};
console.log(res);
}).catch(onerror);
(完)
|