co是一個使
Generator
自動執(zhí)行的函數(shù)庫,設(shè)計的非常精妙佳吞。
如果不知道
Generator
是什么,請看阮一峰的ECMAScript 6入門
koa的中間件實現(xiàn)就是依賴了co,使處理異步代碼寫的像同步代碼一樣,擺脫了回調(diào)地獄
博客地址
co文件非常小,加上注釋就240行,核心代碼就幾十行,其他都是一些輔助函數(shù),比如判段類型和和將array object
等轉(zhuǎn)化成promise
這里我將這都算在toPromise
函數(shù)內(nèi)舰褪。
所以不算這些函數(shù)的話,實際上用上函數(shù)的就這只有co, onFulFilled, next, toPromise
核心代碼如下
function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1);
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
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);
return null;
}
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
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) + '"'));
}
});
}
這里通過co函數(shù)執(zhí)行的gen
的代碼分析一下co函數(shù)的流程
var co = require('./');
function sleep(ms) {
return function(done){
setTimeout(done, ms);
}
}
function *work() {
yield sleep(50);
return 'yay';
}
function* gen() {
var a = yield work;
var b = yield work;
var c = yield work;
return a + b + c;
}
co(gen).then((data) => console.log(data))
這里的代碼選自co
的測試用例
co
函數(shù)傳入一個Generator
類型的gen
并返回Promise
在yield
后面的表達(dá)式或者說異步操作都執(zhí)行結(jié)束后,提供一個鉤子處理函數(shù)的返回值吓歇。
Generator
自執(zhí)行通過兩個函數(shù)onFulfilled和next配合實現(xiàn),首先一開始會執(zhí)行一次onFulfilled()
使gen
函數(shù)開始執(zhí)行
onFulfilled源碼如下
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
console.log(ret);
next(ret);
return null;
}
作用主要是為了捕獲異常和執(zhí)行gen.next
,如果代碼下一次next操作沒有問題,則交給next函數(shù)
處理,反之將異常作為reject
拋出,在這里 第一次執(zhí)行后會在var a = yield work
這里暫停, ret = gen.next(res)
執(zhí)行后 ret會得到的將會是
{ value: [Function: work], done: false } //并作為next參數(shù)執(zhí)行next(ret)
next
主要判定Generator
是否已經(jīng)執(zhí)行結(jié)束,如果結(jié)束返回,反之判斷還未結(jié)束將對yield
后面的表達(dá)式轉(zhuǎn)成promise
然后繼續(xù)執(zhí)行onFulfilled
,
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) + '"'));
}
在這里 ret.done === false
所以將對work
轉(zhuǎn)化成promise
function toPromise(obj) {
if (!obj) return obj;
if (isPromise(obj)) return obj;
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}
代碼如上,通過類型判斷執(zhí)行具體轉(zhuǎn)化函數(shù), 具體可以去看源碼, 另外在next
函數(shù)也寫有目前支持在yield
的表達(dá)式類型為 a function, promise, generator, array, or object
回到next
函數(shù)在這里將work
包裝成Promise
并在Promise
執(zhí)行成功后執(zhí)行onFulfilled
在這里work
的類型是generator
所以co
里實際上執(zhí)行了co(work)
我們測試代碼第二次執(zhí)行onFulfilled
與第一次不同的是,第二次會帶有Promise
返回的值執(zhí)行,而這個值實際上就是work
生成器執(zhí)行結(jié)束后return
的值在這里就是'yay'
ret = gen.next(res); // 第二次執(zhí)行`onFulfilled`后gen.next(res)中的res 就等于'yay';
var a = yield work; //所以在gen.next(res)執(zhí)行后 a = 'yay'
var b = yield work; //開始執(zhí)行 var b = yield work;
如此重復(fù)到第四次,因為var c = yield work;
執(zhí)行完后已經(jīng)沒有下一個yield
,所以第四次執(zhí)行gen.next
函數(shù)返回的ret.done === true
并將return a + b + c;
作為Promise
的resolve
返回慰安。
所以大概流程如下
- 開始執(zhí)行
gen函數(shù)
, 遇到yield
暫停,異步處理yield
后面的表達(dá)式 - 表達(dá)式執(zhí)行結(jié)束后,返回執(zhí)行結(jié)果(resolve),繼續(xù)開始執(zhí)行下一步操作
- 繼續(xù)1的操作,直到函數(shù)結(jié)束