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