環境:v0.10.41
非同期タスクを動的に生成し、Waterfall実行する
Promiseを使ってWaterfallを実現したい時、下記のようにメソッドチェーンで記述することが出来る。
taskA.then(taskB)
.then(function(){
console.log('DONE');
});
しかし、「特定URLのパラメータを20ずつインクリメントしながら5回順次叩く」というような多数の非同期タスクを順次実行したい場合、実行したいタスク個分のthen()
を書かなくてはならず、下記のように記述がとても煩雑になってしまう。(fetch()
は1秒後にresolve()
をコールする。)
var Promise = require('promise');
function fetch(param) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(param);
resolve();
}, 1000);
});
}
(function main() {
var tasks = [0,1,2,3,4].map(function(index) {
return function() {
return fetch(index);
};
});
tasks[0]()
.then(tasks[1])
.then(tasks[2])
.then(tasks[3])
.then(tasks[4])
.then(function() {
console.log('DONE!');
});
})();
そこでPromiseをシンプルにWaterfallで実行する関数イディオム(promiseWaterfall()
)が下のコード。
promiseWaterfall()
はPromiseオブジェクトを返す関数の配列を引数に取り、メソッドチェーンを生成し、最後に実行される関数のPromiseオブジェクトを返す。
var Promise = require('promise');
function promiseWaterfall(tasks) {
var finalTaskPromise = tasks.reduce(function(prevTaskPromise, task) {
return prevTaskPromise.then(task);
}, Promise.resolve());
return finalTaskPromise;
}
function fetch(param) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(param);
resolve();
}, 1000);
});
}
(function main() {
var tasks = [0,1,2,3,4].map(function(index) {
return function() {
return fetch(index);
};
});
promiseWaterfall(tasks).then(function() {
console.log('DONE!');
});
})();
これくらいの内容だと記述量が逆に増えてしまうが、処理が多数ある場合にとても効果を発揮する。
と思ったらv0.11以降(v0.11は--harmony
オプションが必要)はyield
と co というモジュールの組み合わせでより直感的に書けるらしい。
Node.js - 最近のjs非同期処理 PromiseとGeneratorの共存 - Qiita
v0.10の世界で生きていた。。。辛い。
非同期処理を不定回数実行する
次は一定回数の実行ではなく、不定回数の非同期処理を繰り返す処理をPromiseで書く。
下のfetchItems()
は引数で与えられた個数分itemを取得して返す。
fetch()
は要素数3の配列を返すように上述のコードを書き換えたもの。
var Promise = require('promise');
function fetch(param) {
return new Promise(function(resolve) {
setTimeout(function() {
var items = [param, param, param];
console.log('fetch() returns:', items);
resolve(items);
}, 1000);
});
}
function fetchItems(count) {
var items = [];
return new Promise(function(resolve) {
var recursiveFetch = function(index) {
fetch(index).then(function (result) {
items = items.concat(result);
if (items.length > count) {
resolve(items.slice(0, count));
} else {
recursiveFetch(++index);
}
});
};
recursiveFetch(0);
});
}
(function main() {
fetchItems(10).then(function(result) {
console.log('fetchItems() returns:', result);
});
})();
実行結果は下のようにログが1秒おきに1行ずつ表示される。
$ node index.js
fetch() returns: [ 0, 0, 0 ]
fetch() returns: [ 1, 1, 1 ]
fetch() returns: [ 2, 2, 2 ]
fetch() returns: [ 3, 3, 3 ]
fetchItems() returns: [ 0, 0, 0, 1, 1, 1, 2, 2, 2, 3 ]
これもたぶんyeild
とco
を使えば...