はじめに
Promiseで非同期を順番に実行する場合は以下のようにすると思います。
Promise.resolve()
.then(() => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log('1');
resolve();
}, 1000);
});
})
.then(() => {
console.log('2');
});
基本的にはチェーンで書けば問題ないですが、ループで書かなくちゃいけなくなった場合はチェーンで書けないので、うまく書かないといけません。
失敗例
失敗例が以下のようになります。Promiseオブジェクトさえ持って入れば問題ないと思っていたのでPromise.resolve()
したあと順番にセットしていけば裏でよしなにやってくれるものだと思っていました。
const promise = Promise.resolve();
// 1回設定して
promise
.then(() => {
return new Promise((resolve) => {
window.setTimeout(() => {
// 最初に1を出力
console.log('1');
resolve();
}, 1000);
});
});
// また設定する
promise
.then(() => {
// 次に2を出したい
console.log('2');
});
実際は2が先に出てしまいました。どうやら2を出す方もPromise.resolve()
が解決した時の設定になったようです。この挙動は逆に言えばあるPromiseが解決した時に並列して実行することができそうですね(並列実行はPromise.all
でいいですが)
2
1
成功例
チェーンでうまくいっていたのは、毎回新しいPromiseオブジェクトを受け取っていたからのようです。
なので、毎回promiseを受け取っておけばきちんと動作します。
let promise = Promise.resolve();
// キチンと実行結果を受け取る
promise = promise
.then(() => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log('1');
resolve();
}, 1000);
});
});
promise
.then(() => {
console.log('2');
});
1
2
ループを綺麗に書く
上でキチンと動くようになりましたが、毎回代入しないといけなくて、あんまりいい感じがしません。
これを解決する方法として、reduceがあります。第一引数がreturnされたものになるため、毎回Promiseを更新するのにちょうどいいです。
[1, 0].reduce((promise, waitTime, index) => {
return promise
.then(() => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log(index + 1);
resolve();
}, waitTime * 1000);
});
});
}, Promise.resolve());
終わりに
thenでPromiseが返ってくるのは知っていましたが、チェーンで書くためにただ返していたものだと思っていました。実際は新しいPromiseを返していて意外と引っかかったので記事にしました。
一応サンプルコードも以下に置いておきました。画面には何も表示されないのでターミナルで見てもらえると幸いです。
https://jsfiddle.net/wintyo/6bvrsx1w/23/