JavaScript
promise

Promiseを直列実行するちょっとしたhack

More than 1 year has passed since last update.

やりたいこと

  • 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))という、本来実行したかった処理の方だ。もちろんこの後にさらにいくつものthencatchをつなげることもできる。

もう一つのthenconst 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))