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の中で分岐する習慣にしておくと、コードが綺麗になります。
まとめ
個人的には必ずしも上記のようなパターンを直接使っているわけではなく、モジュール化してもうちょっと便利に使っていたりするのですが、とりあえず直列、並列、分岐という基本パターンは覚えておくべきではないかと思います。