轉載請注明: TheViper http://www.cnblogs.com/TheViper
更好的可以看http://purplebamboo.github.io/2014/05/24/koa-source-analytics-1/
co模塊是什么?
不同於promise,它是TJ大神基於ECMAScript 6 generator的異步控制解決方案.
和promise一樣,用co寫出來的異步代碼很優雅,一目了然,擴展性也很強。
由於需要ECMAScript 6環境,所以需要下載版本新點的node.注意,不要去官網下,官網上的是最新的穩定版,要到http://nodejs.org/dist/下。另外現在官網的版本是0.10.35,需要下的是不穩定的0.11.x,至於具體版本號,co隨時都在變,就用最新的好了。最后,運行node的時候要加上--harmony參數。
ECMAScript 6 generator?
比如,
function* run() { console.log("step in child generator") var b = yield 'running'; console.log(b); console.log("step out child generator") } //var runGenerator = run(); function* start() { var a = yield 'start'; console.log(a); yield *run(); var c = yield 'end'; console.log(c); return 'over'; } var it = start(); console.log(it.next());//Object {value: "start", done: false} console.log(it.next(22));//22 step in child generator object {value: 'running', done: false} console.log(it.next(333));//333 step out child generator Object {value: 'end', done: false} console.log(it.next(444));//444 Object {value: "over", done: true}
在function后面加上*表示這是一個generator function。
var it=start();的時候,代碼是沒有開始執行的。it.next();相當於java,python的迭代器。
yield相當於斷點調試里面的單步進入。具體的,
第一次調用it.next();,代碼會執行到第一個yield聲明的地方yield 'start';,此時還沒有對a變量賦值。然后就是console.log返回it.next()的返回值,是一個對象,value是yield語句的值,這個值可以是字符串,函數,對象等,這里是字符串,done代表當前的generator function是否執行完畢。
然后是it.next(22);,這個時候a
賦值語句開始執行,實際上此時yield 'start'
返回的就是22,也就是我們傳的參數。一直執行到yield 'running';
代碼再次斷點停住了。next方法的參數會成為上一個yield的返回值。
輸出22后,繼續執行yield *run();里面的yield 'running';,然后和上面一樣,並沒有執行賦值語句。
yield的參數如果是generator function,那么當執行yield generator function時,會不停的單步進入,直到最里面的那個yield的傳入參數不是generator function為止。
把上面的代碼改一下,增加一個generator function。
function* run1() { console.log("step in child generator1") var b = yield 'running1'; console.log(b); console.log("step out child generator1") } function* run() { console.log("step in child generator") yield * run1(); // console.log(b); console.log("step out child generator") } function* start() { var a = yield 'start'; console.log(a); yield *run(); var c = yield 'end'; console.log(c); return 'over'; } var it = start(); console.log(it.next());//Object {value: "start", done: false} console.log(it.next(22));//22 step in child generator step in child generator1 object {value: 'running1', done: false} console.log(it.next(333));//333 step out child generator1 step out child generator Object {value: 'end', done: false} console.log(it.next(444));//444 Object {value: "over", done: true}
這下一目了然了。
另外,如果generator function里面如果有return,下面的斷點就不再起作用,而是單步跳出,提前返回,並且return的值作為代理調用的返回值。這個后面說co時可以看到有什么用處。
co分析
最新的co版本是4.1,co 4.x是基於promise重寫的,稍微激進了點,這個后面會說到。這里先說co 3.x.
首先看個例子,
var co = require('co'); var fs = require('fs'); function size(file) { return function(fn){ fs.stat(file, function(err, stat){ if (err) return fn(err); fn(null, stat.size); }); } } function *foo(){ var a = yield size('node_modules/thunkify/.npmignore'); var b = yield size('node_modules/thunkify/Makefile'); var c = yield size('node_modules/thunkify/package.json'); return [a, b, c]; } co(function *(){ var results = yield *foo(); console.log(results);//[13,99,1201] return results; })();
size()是個異步操作,依次讀取文件的大小然后返回。
這里的size()是改寫過的,就像jquery里面的deffered,需要改寫成一定的形式才能使用。這種形式叫thunk,是co里面yield可以接受的參數的一種。
可以接受的參數形式
有模塊可以幫我們進行這樣的改寫,那就是thunkify.至於為什么要改寫成這種形式,后面源碼分析就可以看到原因。
參數還可以接受array
co(function *(){ var a = size('.gitignore'); var b = size('index.js'); var c = size('Makefile'); var res = yield [a, b, c]; console.log(res); // => [ 13, 1687, 129 ] })()
object
co(function *(){ var user = yield { name: { first: get('name.first'), last: get('name.last') } }; })()
generator
function *foo(){ var a = yield size('node_modules/thunkify/.npmignore'); var b = yield size('node_modules/thunkify/Makefile'); var c = yield size('node_modules/thunkify/package.json'); return [a, b, c]; } co(function *(){ var results = yield *foo(); console.log(results);//[13,99,1201] return results; })();
promise
co(function* () { var result = yield Promise.resolve(true); return result; }).then(function (value) { console.log(value); }, function (err) { console.error(err.stack); });
Promise是自己構造的對象。
另外,上面所有允許的參數形式都支持嵌套。
function *foo(){ var a = yield size('.gitignore'); var b = yield size('Makefile'); var c = yield size('package.json'); return [a, b, c]; } function *bar(){ var a = yield size('examples/parallel.js'); var b = yield size('examples/nested.js'); var c = yield size('examples/simple.js'); return [a, b, c]; } co(function *(){ var results = yield [foo(), bar()]; console.log(results); })()
如果有回調的話
function *foo(){ var a = yield size('node_modules/thunkify/.npmignore'); var b = yield size('node_modules/thunkify/Makefile'); var c = yield size('node_modules/thunkify/package.json'); return [a, b, c]; } co(function *(){ var results = yield *foo(); console.log(results); return results; })(function (err,args){ console.log(args); });
可以看到,會向回調傳入兩個參數,一個是錯誤信息,一個是返回值的數組。這是在size()里面就定義了的。
function size(file) { return function(fn){ fs.stat(file, function(err, stat){ if (err) return fn(err); fn(null, stat.size); }); } }
里面的fn就是后面co要執行的回調。如果變成fn(stat.size),也就是只有一個參數。
function size(file) { return function(fn){ fs.stat(file, function(err, stat){ if (err) return fn(null); fn(stat.size); }); } } co(function *(){ var results = yield *foo(); console.log(results); return results; })(function (args){ console.log(args);//13 });
這時只會返回第一個異步操作的結果。因為co規定了回調只允許傳入前面說的那兩個參數。如果異步操作的結果是多個值。
function size(file) { return function(fn){ fs.stat(file, function(err, stat){ if (err) return fn(err); fn(null,stat.size,stat.atime); }); } } function *foo(){ var a = yield size('node_modules/thunkify/.npmignore'); var b = yield size('node_modules/thunkify/Makefile'); var c = yield size('node_modules/thunkify/package.json'); return [a, b, c]; } co(function *(){ var results = yield *foo(); console.log(results); return results; })(function (err,args){ console.log(args); });
可以看到結果是被添加到一個數組里面。