やりたいこと
-
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))