co 函數庫是著名程序員 TJ Holowaychuk 於2013年6月發佈的一個小工具,用於 Generator 函數的自動執行。程序員
好比,有一個 Generator 函數,用於依次讀取兩個文件。es6
var gen = function* (){ var f1 = yield readFile('./foo.txt'); var f2 = yield readFile('./bar.txt'); console.log(f1.toString()); console.log(f2.toString()); };
co 函數庫可讓你不用編寫 Generator 函數的執行器。編程
var co = require('co'); co(gen);
上面代碼中,Generator 函數只要傳入 co 函數,就會自動執行。
co 函數返回一個 Promise 對象,所以能夠用 then 方法添加回調函數。數組
co(gen).then(function(){ console.log('generator函數執行完畢了'); });
爲何 co 能夠自動執行 Generator 函數?
前面文章說過,Generator 函數就是一個異步操做的容器。它的自動執行須要一種機制,當異步操做有告終果,可以自動交回執行權。
兩種方法能夠作到這一點。promise
co 函數庫其實就是將兩種自動執行器(Thunk 函數和 Promise 對象),包裝成一個庫。使用 co 的前提條件是,Generator 函數的 yield 命令後面,只能是 Thunk 函數或 Promise 對象。併發
上一篇文章已經介紹了基於 Thunk 函數的自動執行器。下面來看,基於 Promise 對象的自動執行器。這是理解 co 函數庫必須的。異步
仍是沿用上面的例子。首先,把 fs 模塊的 readFile 方法包裝成一個 Promise 對象。異步編程
/** * 基於Promise的自動執行器 */ var fs = require('fs'); var readFile = function(fileName) { return new Promise(function(resolve, reject) { fs.readFile(fileName, function(err, data) { if(err) reject(err); resolve(data); }); }); }; var gen = function* (){ var f1 = yield readFile('./module-i.es'); var f2 = yield readFile('./module-i.js'); console.log(f1.toString()); console.log(f2.toString()); };
而後,手動執行上面的 Generator 函數。函數
var g = gen(); g.next().value.then(function(data) { g.next(data).value.then(function(data){ g.next(data); }); });
手動執行其實就是用 then 方法,層層添加回調函數。理解了這一點,就能夠寫出一個自動執行器。工具
function run (gen) { var g = gen(); function next (data) { var result = g.next(data); if(result.done) return result.value; result.value.then(next); } next(); } run(gen);
上面代碼中,只要 Generator 函數還沒執行到最後一步,next 函數就調用自身,以此實現自動執行。
co 就是上面那個自動執行器的擴展,它的源碼只有幾十行,很是簡單。
首先,co 函數接受 Generator 函數做爲參數,返回一個 Promise 對象。
function co(gen) { var ctx = this; return new Promise(function(resolve, reject){ //在返回的 Promise 對象裏面,co 先檢查參數 gen 是否爲 Generator 函數。若是是,就執行該函數,獲得一個內部指針對象;若是不是就返回,並將 Promise 對象的狀態改成 resolved 。 if(typeof gen === 'function') { gen = gen.call(ctx); } if(!gen || typeof gen.next !== 'function') return resolve(gen); }); //接着,co 將 Generator 函數的內部指針對象的 next 方法,包裝成 onFulefilled 函數。這主要是爲了可以捕捉拋出的錯誤。 onFulfilled(); function onFulfilled (res) { var ret; try { ret = gen.next(res); }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) + '"')); } }); }
上面代碼中,next 函數的內部代碼,一共只有四行命令。
co 支持併發的異步操做,即容許某些操做同時進行,等到它們所有完成,才進行下一步。
這時,要把併發的操做都放在數組或對象裏面。
// 數組 co(function* (){ var res = yield [ Promise.resolve(1); Promise.resolve(2); ]; console.log(res); }).catch(onerror); // 對象 co(function* (){ var res = yield { '1': Promise.reolve(1); '2': Promise.resovle(2); }; console.log(res); }).catch(onerror);