0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScript勉強会 第6回補足 ~非同期処理で気をつけること~

Last updated at Posted at 2022-03-22

宙に浮いたPromiseを作らない

宙に浮いたPromiseというのは、awaitのつけれられていない非同期処理の呼び出しがある関数(もしくはPromisereturnしていない関数)です。

具体的には以下のようなものです

.ts
async function sendRequest(){
  await postA()
  postB()
}

このsendRequest() 関数をawaitして呼び出してみると、postA()関数はきちんと処理完了まで待ってくれますが、postB()は待ってくれなくなります。

.ts
await sendRequest() // => postAの完了を待機するが、postB()の完了は待機してくれない
// 本当はpostB()の実行を待って実行したかった処理
doSomething()

このような関数を作ってしまうと、非同期処理のハンドリングが非常に困難になってしまうので、基本的にはawaitをつけるなり、returnで返すなりして、きちんと呼び出し元からハンドリングできるようにしましょう。

並列実行しつつPromiseを制御する

Promiseを並列実行しつつ、きちんと制御するにはPromise.allPromise.allSettledを使います。
これらの機能を使うことで、宙に浮いたPromise関数の呼び出しを作らずに、複数の非同期処理を非同期に実行することができます。

.ts

await Promise.all([
 postA(),
 postB()
])

// postAとpostBが並列に実行され、両方が正常終了する、もしくは、どちらかがエラーになったら処理が終了し、ここが実行される
doSomething()

Promise.allSettledは割と最近の構文なので使えないプロジェクトもあることに注意

.ts

await Promise.allSettled([
 postA(),
 postB()
])

// postAとpostBが並列に実行され、両方の処理が正常に終了するかエラーになるかし、すべての結果が決まったあと、ここが実行される
doSomething()

非同期処理の繰り返し処理

非同期処理の繰り返す処理は少し注意が必要です。よくある悪い例は以下です。

悪い例.ts
[user1,user2].forEach(async (user) => {
  await sendUser(user)
})

上記のようにforEachmapを使ってPromise関数を引数に指定した場合、JSは引数に指定された関数をawaitをつけずに実行します。

なので上記は以下と等価です。Promiseが宙に浮いてしまっている悪い例ですね

.ts
 (() => await sendUser(user1))()
 (() => await sendUser(user2))()

 // 少しわかりにくいが以下とだいたい同じ
 sendUser(user1)
 sendUser(user2)

一方for文を使って以下のように書くこともできます。

悪い例2.ts
for (const user of [user1,user2]) {
 await sendUser(user)
}

for文はforEachと違い、関数の呼び出しを行うわけではないので、上記は以下と等価です。

.ts
await sendUser(user1)
await sendUser(user2)

間違っているわけではないのですが、この書き方だとすべてのループが直列に実行されるので、ループの回数が多い場合は性能に悪影響が出る可能性があります。このため、ESLintではno-await-in-loopというルールで禁止されていることが多いです。

すべてのループの非同期処理を並列に実行して、すべての実行結果の完了を待つことを考えるのであれば、以下のようなコードを書くのが良いと思います。forEachではなくmapを使っているところがポイントです。

良い例.ts
// ここではallSettledを使っているが、環境や目的によってはallでもほぼ同じ使い方ができる
await Promise.allSettled(
 [user1,user2].map((user) => {
   return sendUser(user) // returnでPromiseを返しているが、`await sendUser(user)`のようにしても良い
 })
)

これは以下のコードと等価です

.ts
await Promise.allSettled([
 sendUser(user1),
 sendUser(user2) 
])
0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?