前言
了解
co的前提是?已經(jīng)知曉generator是什么,可以看軟大神的Generator 函數(shù)的語法,
co是TJ大神寫的?能夠使generator自動執(zhí)行的函數(shù)庫,而我們熟知的koa也用到了它管理異步流程控制,將異步任務(wù)書寫同步化,爽的飛起,也擺脫了一直以來的回調(diào)地獄問題。

如何使用
首先我們根據(jù)co的官方文檔來稍做改變看下,到底如何使用co,再一步步進行源碼分析工作(這篇文章分析的co版本是
4.6.0)。
yield 后面常見的可以跟的類型
- promises
- array (parallel execution)
- objects (parallel execution)
- generator functions (delegation)
promises
let co = require('co')
let genTimeoutFun = (delay) => {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`delayTime: ${delay}`)
}, delay)
})
}
}
let timeout1 = genTimeoutFun(1000)
let timeout2 = genTimeoutFun(200)
co(function * () {
let a = yield timeout1()
console.log(a) // delayTime: 1000
let b = yield timeout2()
console.log(b) // delayTime: 200
return 'end'
}).then((res) => {
console.log(res)
})
array
let co = require('co')
let genTimeoutFun = (delay) => {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`delayTime: ${delay}`)
}, delay)
})
}
}
let timeout1 = genTimeoutFun(1000)
let timeout2 = genTimeoutFun(200)
co(function * () {
let a = yield [timeout1(), timeout2()]
console.log(a) // [ 'delayTime: 1000', 'delayTime: 200' ]
return 'end'
}).then((res) => {
console.log(res) // end
})
objects
let co = require('co')
let genTimeoutFun = (delay) => {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`delayTime: ${delay}`)
}, delay)
})
}
}
let timeout1 = genTimeoutFun(1000)
let timeout2 = genTimeoutFun(200)
co(function * () {
let a = yield {
timeout1: timeout1(),
timeout2: timeout2()
}
console.log(a) // { timeout1: 'delayTime: 1000',timeout2: 'delayTime: 200' }
return 'end'
}).then((res) => {
console.log(res) // end
})
generator functions
let co = require('co')
let genTimeoutFun = (delay) => {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`delayTime: ${delay}`)
}, delay)
})
}
}
let timeout1 = genTimeoutFun(1000)
let timeout2 = genTimeoutFun(200)
function * gen () {
let a = yield timeout1()
console.log(a) // delayTime: 1000
let b = yield timeout2()
console.log(b) // delayTime: 200
}
co(function * () {
yield gen()
return 'end'
}).then((res) => {
console.log(res) // end
})
最后說一下,關(guān)于執(zhí)行傳入的generator函數(shù)接收參數(shù)的問題
let co = require('co')
co(function * (name) {
console.log(name) // qianlongo
}, 'qianlongo')
從co函?數(shù)的第二個參數(shù)開始,便是傳入的generator函數(shù)可以接收的實參
開始分析源碼
你可以把以上代碼拷貝至本地測試一番看看效果,接下來我們一步步開始分析co的源碼
首先經(jīng)過上面的例子可以發(fā)現(xiàn),co函數(shù)本身接收一個generator函數(shù),并且co執(zhí)行后返回的是Promise
function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1)
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
// xxx
});
}
在Promise的內(nèi)部,先執(zhí)行了外部傳入的gen,執(zhí)行的結(jié)果如果不具備?next屬性(且要是一個函數(shù)),就直接返回,并將執(zhí)行成功回調(diào)resolve(gen),否則得到的是一個指針對象。
接下來繼續(xù)看...
onFulfilled();
/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res); // 用上面執(zhí)行g(shù)en之后的generator生成器將指針指向下一個位置
} catch (e) {
return reject(e);
}
next(ret); // 緊接著執(zhí)行next,正是它實現(xiàn)了反復(fù)調(diào)用自己,自動流程控制,注意ret(即上一次gen.next執(zhí)行后返回的對象{value: xxx, done: true or false})
}
/**
* @param {Error} err
* @return {Promise}
* @api private
*/
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
我覺得可以把 onFulfilled 和 onRejected 看成是返回的Promise的resolve和reject。
而onFulfilled也是將原生的generator生成器的next方法包裝了一遍,大概是為了抓取錯誤吧(看到內(nèi)部的try catch了嗎)
好,我們看到了co內(nèi)部將指針移動到了第一個位置之后,接著執(zhí)行了內(nèi)部的next方法,接下來聚焦在該函數(shù)上
function next(ret) {
// 如果整個generator函數(shù)的內(nèi)部狀態(tài)已經(jīng)表示走完,便將Promise的狀態(tài)設(shè)置為成功狀態(tài),并執(zhí)行resolve
if (ret.done) return resolve(ret.value);
// 這一步是將ret的value轉(zhuǎn)換為Promise形式
var value = toPromise.call(ctx, ret.value);
// 這里非常關(guān)鍵,是co實現(xiàn)自己調(diào)用自己,實現(xiàn)流程自動化的關(guān)鍵
// 注意這里使用value.then,即為返回值添加成功和失敗的回調(diào),在成功的回調(diào)里面再去執(zhí)行onFulfilled,緊接著就是調(diào)用內(nèi)部的next函數(shù)
// 那不是就保證了流程完全按照你寫的順序來了?
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
// 拋出錯誤,yield后只能跟著指定的下列這幾種類型
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
聰明的你,是不是已經(jīng)明白了co是怎么將異步流程自動管理起來了
但是我對next函數(shù)中的toPromise函數(shù)還有疑問,他到底做了什么事?使得co(generatorFun)中yield可以支持數(shù)組、對象、generator函數(shù)等形式。
一步步來看
function toPromise(obj) {
// obj不存在,直接返回
if (!obj) return obj;
// 如果obj已經(jīng)是Promise,則也是直接返回
if (isPromise(obj)) return obj;
// 如果是個generator函數(shù)或者generator生成器,那就像你自己調(diào)用co函數(shù)一樣,手動傳到co里面去執(zhí)行
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
// 如果obj既不是Promise,也不是isGeneratorFunction和isGenerator,要是一個普通的函數(shù)(需要符合thunk函數(shù)規(guī)范),就將該函數(shù)包裝成?Promise的形式
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
// 如果是一個數(shù)組的形式,就去arrayToPromise包裝一番
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}
首先如果obj不存在,就直接返回,你想啊,co本來就是依賴上一次指針返回的value是Promise或者其他,這個時候如果返回
{
value: false,
done: false
}
那就沒有必要再給一個false值轉(zhuǎn)成Promise形式了吧。
接著,如果obj本身就是個Promise也是直接返回,用了內(nèi)部的isPromise函數(shù)進行判斷,我們看下他怎么實現(xiàn)的。
function isPromise(obj) {
return 'function' == typeof obj.then;
}
其實就是判斷了obj的then屬性是不是個函數(shù)
再接著,如果是個generator函數(shù)或者generator生成器,那就像你自己調(diào)用co函數(shù)一樣,手動傳到co里面去執(zhí)行。
isGeneratorFunction
function isGeneratorFunction(obj) {
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
return isGenerator(constructor.prototype);
}
通過obj的constructor屬性去判斷其是否屬于GeneratorFunction,最后如果constructor屬性沒判斷出來,再去用isGenerator,判斷obj的原型是不是generator生成器
function isGenerator(obj) {
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}
判斷的條件也比較直接,?需要符合兩個條件,一個是obj.next要是一個函數(shù),一個是obj.throw要是一個函數(shù)
接下來繼續(xù)看
如果obj既不是Promise,也不是isGeneratorFunction和isGenerator,要是一個普通的函數(shù),就將該函數(shù)包裝成?Promise的形式,這里我們主要需要看thunkToPromise
function thunkToPromise(fn) {
var ctx = this;
// 將thunk函數(shù)包裝成Promise
return new Promise(function (resolve, reject) {
// 執(zhí)行這個thunk函數(shù)
fn.call(ctx, function (err, res) {
// 注意thunk函數(shù)內(nèi)部接收的回調(diào)函數(shù)中傳入的第一個參數(shù)是err,出現(xiàn)了err,?當然需要走reject了
if (err) return reject(err);
// 參數(shù)是兩個以上的情況下,將參數(shù)整成一個數(shù)組
if (arguments.length > 2) res = slice.call(arguments, 1);
// 最后執(zhí)行成功的回調(diào)
resolve(res);
});
});
}
接下來是重頭戲了,co中如果處理yield后面跟一個數(shù)組呢?主要是arrayToPromise函數(shù)的作用
function arrayToPromise(obj) {
// 使用到了Promise.all,將obj中多個promise實例(當然你也可以在數(shù)組中填thunk函數(shù),generator函數(shù)等)重新包裝成一個。最后返回一個新的Promise
return Promise.all(obj.map(toPromise, this));
}
還有最后一個判斷,如果obj是個對象怎么辦?
function objectToPromise(obj){
// 構(gòu)造一個和傳入對象有相同構(gòu)造器的對象, results也是
var results = new obj.constructor();
// 獲取obj的keys
var keys = Object.keys(obj);
// 存儲obj中是Promise的屬性
var promises = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var promise = toPromise.call(this, obj[key]);
// 如果是結(jié)果是Promise,則用defer函數(shù)對results進行修改
if (promise && isPromise(promise)) defer(promise, key);
// 如果是非Promise就按原樣返回
else results[key] = obj[key];
}
// 最后 使用到了Promise.all,將obj中多個promise實例
return Promise.all(promises).then(function () {
return results;
});
function defer(promise, key) {
// predefine the key in the result
results[key] = undefined;
promises.push(promise.then(function (res) {
// 運行成功之后再講結(jié)果賦值給results
results[key] = res;
}));
}
}
結(jié)尾
到這里,co源碼分析就告一段落了。總感覺有些沒有說到位,?歡迎大家拍磚,晚安。