やりたいこと
-
Promiseを一つずつ順番に実行する (並列ではなく直列に実行) -
Promiseの開始は非同期で行う (メイン側のコードは止まらない)
サンプルとなるPromise
const create = v => new Promise(resolve => {
console.log(`create ${v}`)
setTimeout(() => {
console.log(`done ${v}`)
resolve(v)
}, 1000)
})
createは、1秒待つというPromiseを生成する関数だ。これを実際の処理、たとえばHTTPを取ってくるPromiseだったりとかに置き換える事になる。
今回の目的を果たす関数
/**
* creator()が生成するPromiseを非同期かつ直列的に実行する
* var creator Promiseを返すコールバック
* return creatorが生成したPromise
*/
let p = Promise.resolve()
const doSerial = creator => p = p.then(creator)
pは、直列に実行する為のPromiseを保持をする為の変数だ。実行時に書き換える必要があるのでconstではなくletで定義している。
doSerial(() => create('p1')).then(res => console.log(res))
doSerial(() => create('p2')).then(res => console.log(res))
doSerial(() => create('p3')).then(res => console.log(res))
doSerial(creator)はcreatorの生成するPromiseが帰ってくるので、そこにthenをつなげることで処理ができる。
ポイント
create()を実行する、つまりnew Promise()でインスタンスを生成した段階ですでにsetTimeout()が走ってしまう。そのため、直列に実行しようと思うと、そもそもcreate()自体を制御の中で直列実行する必要がある。そのため、doSerial(creator)のcreator引数で、Promise生成関数を渡すという、多少面倒な事をしなければならない。
次に、Promiseにぶら下げるthenは、実は平行でぶら下げることができる。その場合複数ぶらさげたthenはそれぞれ平行で走ることになる(JSの仕組み上順列はあるにせよ)。つまり、setTimeoutを待つPromiseならば、setTimeoutが完了してから、それらのthenが並列で走る。
const p = Promise.resolve()
const p1 = p.then(() => create('p1'))
p1.then(res => console.log(res))
const p2 = p1.then(() => create('p2'))
p2.then(res => console.log(res))
展開するとしたら、このようなコードになる。p1は、create('p1')を行うPromiseだ。元々Promise.resolve()で生み出されてるので、待ちが発生せずにいきなりcreate('p1')が実行される。そしてp1には二つのthenがぶら下がっている。まずはp1.then(res => console.log(res))という、本来実行したかった処理の方だ。もちろんこの後にさらにいくつものthenやcatchをつなげることもできる。
もう一つのthenは const p2 = p1.then(() => create('p2')) というもので、p1の結果を待って、次のcreate('p2')を行うものだ。
おさらい
const create = v => new Promise(resolve => {
console.log(`create ${v}`)
setTimeout(() => {
console.log(`done ${v}`)
resolve(v)
}, 1000)
})
let p = Promise.resolve()
const doSerial = creator => p = p.then(creator)
doSerial(() => create('p1')).then(res => console.log(res))
doSerial(() => create('p2')).then(res => console.log(res))
doSerial(() => create('p3')).then(res => console.log(res))