Promiseの静的メソッドの使い方まとめ
非同期処理について
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
Discussion