LoginSignup
7
3

More than 5 years have passed since last update.

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

Posted at

やりたいこと

  • 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))
7
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3