Promiseの静的メソッドの使い方まとめ

28061 ワード

非同期処理について

JavaScriptにおいては、時間がかかる処理はその実行結果を待たずに後続の処理を先行させるノンブロッキングな処理がデフォルトになっています。
これらの処理は非同期処理と呼ばれ、callback関数を使用したり、Promiseオブジェクトを使用したり、async/awaitを使用したりとその扱い方は変化してきました。

筆者はasync/awaitばかりを多用しています。しかし、例えば、複数のテーブルへの書き込み、複数の外部APIからデータを取得してジョインするなど、非同期処理を並列で実行させたい場合などがあり、その場合に一つ一つasync/awaitをしていたのでは、JavaScriptの特性を活かすことができていませんし、例外処理の取り回し等が冗長になる場合があります。

今回はそれらの解決につながるPromiseの静的メソッドについてまとめてみました。

準備

まずはPromiseを返すサンプルの関数を作る

// msを受け取る
const sleep = (ms: number) => {
  // Promiseコンストラクタは一つの型引数と一つのexecutor関数を取ります。
  // excutor関数では、第一引数にfullfilledしたときのコールバック関数を第二引数にrejectedされたときのコールバック関数を受けます。慣例的に、resolve, rejectと命名します。
  return new Promise<string>((resolve, reject) => {
    // rejectされた時の動作を検証したいので、便宜的に追加しました。
    if (ms > 2000) {
      reject('Too Long to wait')
    }
    setTimeout(() => {resolve('success!!')}, ms)
  })
}

console.timeで処理時間を測定する

console.timeから計測を開始して、console.timeEnd()で計測を終了します。詳しい使い方はこちら

console.time()
sleep(1000).then(data => {
  console.log(data)
  console.timeEnd()
})

Promise.all

promiseを配列で受けて、全ての結果がfulllfiledされるのを待ちます。
rejectされたら、その時点で.catchへ流れます。

全てfullfilledされる場合

console.time()
const p1 = sleep(1000)
const p2 = sleep(1500)
const p3 = sleep(2000)

const promiseAll = Promise.all([p1, p2, p3])

promiseAll.then(data => {
  console.log(data)
}).catch(err => {
  console.log(`err: ${err}`)
}).finally(() => {
  console.timeEnd()
})

最長のp3が終わるまで待っているので終了までに2,000msほどかかっています。

$ node dist/index.js
[ 'success!!', 'success!!', 'success!!' ]
default: 2.018s

一部rejectされる場合

p3により、Promiseが作成されるとともにrejectされます。
一つでもrejectされた場合は.catchに流れます。

console.time()
const p1 = sleep(1000)
const p2 = sleep(1500)
const p3 = sleep(3000) // rejectされる

const promiseAll = Promise.all([p1, p2, p3])

promiseAll.then(data => {
  console.log(data)
}).catch(err => {
  console.log(`err: ${err}`) // こちらに流れる
}).finally(() => {
  console.timeEnd()
})
$ node dist/index.js
err: Too Long to wait
default: 6.508ms

Promise.race

一番最初にsetteledされた結果でPromise.raceのPromiseがsettledされます。

全てfullfilledされる場合

console.time()
const p1 = sleep(1000)
const p2 = sleep(1500)
const p3 = sleep(2000) 

const promiseRace = Promise.race([p1, p2, p3])

promiseRace.then(data => {
  console.log(data)
}).catch(err => {
  console.log(`err: ${err}`)
}).finally(() => {
  console.timeEnd()
})

p1が解決された時点でPromise.raceのPromiseが解決されて、全体の処理がおよそ1秒で終了します。

$ node dist/index.js
success!!
default: 1.008s

一部rejectされる場合

console.time()
const p1 = sleep(1000)
const p2 = sleep(1500)
const p3 = sleep(3000) // rejectされる

const promiseRace = Promise.race([p1, p2, p3])

promiseRace.then(data => {
  console.log(data)
}).catch(err => {
  console.log(`err: ${err}`)
}).finally(() => {
  console.timeEnd()
})
$ node dist/index.js
err: Too Long to wait
default: 7.745ms

Promise.allSettled

Promise.allでは処理がrejectされた時点で他の処理を待たず、Promise.allが返すPromiseがsettledされてしまいますが、失敗は失敗でおいておいて、他の処理の終了を待ちたい場合があります。その際にPromise.allSettledを使用します。

全てfullfilledされる場合

console.time()
const p1 = sleep(1000)
const p2 = sleep(1500)
const p3 = sleep(2000)

const promiseRace = Promise.allSettled([p1, p2, p3])

promiseRace.then(data => {
  console.log(data)
}).catch(err => {
  console.log(`err: ${err}`)
}).finally(() => {
  console.timeEnd()
})

処理時間等はPromise.allと同じ理屈ですが、返答が以下のようになります。

$ node dist/index.js
[
  { status: 'fulfilled', value: 'success!!' },
  { status: 'fulfilled', value: 'success!!' },
  { status: 'fulfilled', value: 'success!!' }
]
default: 2.026s

一部rejectされる場合

console.time()
const p1 = sleep(1000)
const p2 = sleep(1500)
const p3 = sleep(3000) // rejectされる

const promiseRace = Promise.allSettled([p1, p2, p3])

promiseRace.then(data => {
  console.log(data)
}).catch(err => {
  console.log(`err: ${err}`)
}).finally(() => {
  console.timeEnd()
})

p3は即座に失敗しており、p2を待っておよそ1.5秒で処理が終了しています。

$ node dist/index.js
[
  { status: 'fulfilled', value: 'success!!' },
  { status: 'fulfilled', value: 'success!!' },
  { status: 'rejected', reason: 'Too Long to wait' }
]
default: 1.524s