q
q模組是實作Promises/A+ Spec規範的一個模組,主要目的是透過豐富的流程語意來定義非同步的流程操作。其他實作promises套件還有像when.js等,可參考:http://wiki.commonjs.org/wiki/Promises/A。
官方網站
https://github.com/kriskowal/q
Installation
npm install q
Sample Usage
下面範例展示基本的q操作方式,透過request module的callback function,讓延遲效果看起來比較有感(因為每個網站在每個request下,回應時間其實是不固定的):
var Q = require('q') , request = require('request') , queue = ['http://www.google.com','http://micloud.tw','http://tw.yahoo.com']; var fn = function(url) { var deferred = Q.defer(); request.get(url, function(e,r,d){ if(e) console.log(e); console.log('[%s] word count:%s', url, d.length); deferred.resolve(); }); return deferred.promise; }; //透過allResolved,將傳入的function執行完成後,才會進入.then() Q.allResolved([ fn(queue[0]), fn(queue[1]), fn(queue[2]) ]) .then(function(){ console.log('end...') }) .done();
範例結果:
$ node sample01
[http://www.google.com] word count:44919
[http://tw.yahoo.com] word count:301355
[http://micloud.tw] word count:37213
end...
上面範例中,其實每個被傳入的function都實際被執行完,但是因為使用request module,造成回傳時間延遲不一致,在allowResolved中的各個function實際回傳結果仍然順序與預期不一致,但是在then中的function就會保證在之後執行!
在sequencial的程式中,另外個重要的課題是將數個Job完成後,在進行整體的回傳值計算動作,在q中,可以透過spread()來做到這件事情:
var Q = require('q') , request = require('request') , queue = ['http://micloud.tw','http://www.google.com','http://tw.yahoo.com']; var fn = function(url) { //在function中定義Q.defer() var deferred = Q.defer(); //console.log('start of %s', url); request.get(url, function(e,r,d){ if(e) console.log(e); console.log('[%s] word count:%s', url, d.length); //如果事件完成,則執行resolve() deferred.resolve(d.length); }); return deferred.promise; }; var out = function (x, y, z) { var d = Q.defer(); console.log('x:%s, y:%s, z:%s', x, y, z); d.resolve(); return d.promise; }; //透過allResolved,將傳入的function執行完成後 //再使用spread()擷取回傳值,然後才會進入.then() Q.allResolved([fn(queue[0]), fn(queue[1]), fn(queue[2])]) .spread(out) .then(function(){ console.log('end...'); }) .done();
範例結果:
$ node sample02
[http://www.google.com] word count:44919
[http://tw.yahoo.com] word count:302985
[http://micloud.tw] word count:37213
x:37213, y:44919, z:302985
end...
下面是一個更複雜的範例,可以知道q在流程上的控制,如果不是透過擷取callback,仍是只有控制啟動時間的效果...
var Q = require("q"); var oneA = function () { var d = Q.defer(); var timeUntilResolve = 1500; //預期比oneB更晚結束 console.log('1A Starting'); setTimeout(function () { console.log('1A Finished'); d.resolve('1ATime: ' + timeUntilResolve); }, timeUntilResolve); return d.promise; }; var oneB = function () { var d = Q.defer(); var timeUntilResolve = 1000; console.log('1B Starting'); setTimeout(function () { console.log('1B Finished'); d.resolve('1BTime: ' + timeUntilResolve); }, timeUntilResolve); return d.promise; }; // This fuction throws an error which later on we show will be handled var two = function (oneATime, oneBTime) { var d = Q.defer(); console.log('OneA: ' + oneATime + ', OneB: ' + oneBTime); console.log('2 Starting and Finishing, so 3A and 3B should start'); d.resolve(); return d.promise; }; var threeA = function () { var d = Q.defer(); console.log('3A Starting'); setTimeout(function () { console.log('3A Finished'); d.resolve(); }, Math.floor((Math.random()*2000)+1)); return d.promise; }; var threeB = function () { var d = Q.defer(); console.log('3B Starting'); setTimeout(function () { console.log('3B Finished'); d.resolve(); }, Math.floor((Math.random()*5000)+1)); return d.promise; }; var four = function () { console.log('Four is now done'); }; Q.allResolved([ oneA(), oneB() ]) .spread(two) .then(function () { return Q.all([ threeA(), threeB() ]); }) .then(four) .done();
此範例可以明顯看到promise.allResolved相似的實作,其實只能保證觸發的時間,透過spread與then來操控順序才是正確。
範例結果:
$ node sample03
1A Starting
1B Starting
1B Finished
1A Finished
OneA: 1ATime: 1500, OneB: 1BTime: 1000
2 Starting and Finishing, so 3A and 3B should start
3A Starting
3B Starting
3A Finished
3B Finished
Four is now done