ES6形式のPromiseを使うときに頻出する3つのパターン。直列パターン、並列パターン、分岐パターンを説明します。
最近、Promise周りが盛り上がっていて、reduceを使ったほうが良いとか、ライブラリがどうとか・・・いう話を聞くのですが、そもそも「ベタに書いたときにどうするのが基本なのか」という情報が見つからないので書いてみました。
直列パターン
一番良く使うのは、複数の処理を直列につなげるパターンでしょう。#1が終わってから、#2、#2が終わってから#3というパターンです。
Promise.resolve()
.then(function(){
return new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
})
})
.then(function(){
return new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
})
})
本来、最初のPromise.resolve()は不要で、new Promiseで始めた方がコードは短くなります。しかし、自分はわざわざコーディングルールとして、Promise.resolve()で始めて、.thenで受けるようにしています。なぜでしょうか?それは、コードのパターンを統一するためです。#1, #2, #3と3つの処理が意味的には対等であるにも関わらず、#1だけ書き方が違うと、コードも見づらいし、処理の順番を変更したとき(あるいは、後述するように分岐や並列化を入れる場合)直感的ではないコード修正が必要になります。このため、上のように最初にダミーのPromise.resolve()を入れるようにしています。
並列パターン
ここに並列処理(Promise.all)を組みわせる場合、以下のようになります。#1と#2を並列に処理し、両方が終わったら#3を行うというパターンです。
Promise.resolve()
.then(function(){
return Promise.all([
new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
}),
new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
})
])
})
.then(function(){
return new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
})
});
並列パターン(map使用)
ただし、上のように複数のPromiseをそのまま書くことはあまり多くなく、以下のようにmapを使ってPromiseを生成することが多いです。
Promise.resolve()
.then(function(){
return Promise.all(list.map(function(item){
return new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
}),
}))
})
.then(function(){
return new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
})
});
分岐パターン
直列パターンで、条件によって処理内容を変えたり、処理をスキップしたいというときに使います。
Promise.resolve()
.then(function(){
if(condition1){
// 条件1 単独のPromise
return new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
})
}else if(condition2){
// 条件2 Promise2つを直列化
return Promise.resolve()
.then(function(){
return new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
})
})
.then(function(){
return new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
})
})
}else{
// 条件3 何もしない
}
})
.then(function(){
return new Promise(function(fulfilled, rejected){
asyncFunc(function(){
fulfilled();
});
})
});
ポイントは、.thenの中で、分岐しているということです。もちろん、同じことは、new Promiseの中で分岐させてもできます。ただ、new Promise(fulfilled, rejected){}という塊は、できる限りシンプルな非同期処理の単位であるべきなので、フロー制御は外側に追い出すのが良いと思います。Promiseを返すモジュールを利用する場合も、.thenの中で分岐する習慣にしておくと、コードが綺麗になります。
まとめ
個人的には必ずしも上記のようなパターンを直接使っているわけではなく、モジュール化してもうちょっと便利に使っていたりするのですが、とりあえず直列、並列、分岐という基本パターンは覚えておくべきではないかと思います。