Posted at

async.js を捨てて Promise を使おう!

More than 3 years have passed since last update.

async.js が嫌いな訳ではありません。今までお世話になっていました。

Node.js v0.12 から Promise が標準で使えるようになったので async.js の parallelseries で行っていたことを Promise で実現しよう!という趣旨です。


parallel (並列実行)

var count = 0;

function echo() {
var cnt = count++;
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(cnt);
resolve(cnt);
}, Math.random() * 1000);
});
}

var promises = [echo(), echo(), echo()];

Promise.all(promises).then(function (values) {
// 成功時処理 (実行順に関わらず values は [0, 1, 2])
console.log("Succeeded!", values);
}).catch(function (err) {
// 例外処理
console.error("Failed.", err);
});

シングルスレッドなので正確には並列ではありませんが、処理が終了したものから順に出力されるため、実行する毎に表示される数字の順番が変わります。


series (直列実行)

var count = 0;

function echo() {
var cnt = count++;
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(cnt);
resolve(cnt);
}, Math.random() * 1000);
});
}

var tasks = [echo, echo, echo];

tasks.reduce(function (a, b) {
return a.then(b);
}, Promise.resolve(null)).then(function () {
// 成功時処理
console.log("Succeeded!");
}).catch(function (err) {
// 例外処理
console.error("Failed.", err);
});

こちらは、必ず 0, 1, 2 の順に処理が走ります。

前の処理が終わるまで次の関数を評価しない為です。


まとめ

Promise はインスタンスが生成された時点で渡された関数を評価するため、直列実行したい場合は関数でラップする必要があります。

Array#reduce メソッドを利用することで、複数の Promise を返す関数を纏めて 1 つの Promise に集約することが出来ます。

Array#reduce の第 2 引数に初期値として Promise を渡します。このように記述すると、 tasks 配列が空の場合でも正しく処理が進みます。


直列実行の際に値を配列で受け取りたい

tasks.reduce(function (a, b) {

return a.then(function (values) {
return b().then(function (value) {
return Promise.resolve(values.concat([value]));
});
});
}, Promise.resolve([])).then(function (values) {
// 成功時処理
console.log("Succeeded!", values);
}).catch(function (err) {
// 例外処理
console.error("Failed.", err);
});

そんな需要もあるかと思い、書いておきます。

reduce の中が複雑になりますが、このように記述することで echo 関数に変更を加えずに要件を満たせます。


参考