async, awaitでの非同期処理の書き方

asyncやらawaitやら
非同期処理をする時に使うんだろうなーというくらいの認識でいたけどちゃんと分かってなかったのでまとめる。

目次

そもそも同期処理と非同期処理とは

同期処理

リクエストした後、レスポンスが返ってくるまで待ってる処理

非同期処理

リクエストした後、レスポンスが返ってくるのを待たずにその後の処理を継続する。
レスポンスが返ってきた時にその処理の続きを行う。

定義としては違うんだろうけど、特徴としてはこんな感じ。
処理待ちの間に別の処理を行いたい時には非同期処理にするとよい。

ご飯炊けるの待ってる間に味噌汁作る的な感じ。

非同期処理の中にも逐次処理と並行処理がある

ここがちゃんと分かってなかった。

逐次処理は分かりやすい。
下記のような感じでメソッドチェーンを使って処理1→処理2→処理3と順番に処理を行う。

これって同期処理じゃないのって感じだったけど、あくまで非同期処理の中で同期的に処理を行うという認識でいる。

myPromise(処理1).then(処理2).then(処理3)

並行処理はいくつかの非同期的な処理を並行して行う。
つまり複数の処理を同時並行で行って、それが全部終わった段階で初めて次の処理を行うというもの。

たとえば目玉焼きカレーを作る時に

  • ご飯を炊く
  • カレーを作る

という操作を同時に行って両方終わった後で皿に盛り付けて、最後に目玉焼きを乗せる的なイメージ。

同期処理と非同期処理のまとめはこんな感じ。

そしてここからが本題。業務で非同期処理を行ったがうまくいかなかった。

結論、並行処理とmapメソッド内での非同期処理がうまく書けてなかったので書き残しておく。

まずは基本的な逐次処理から

非同期処理はpromise使って.thenとか.catchとかやるんだけどそれをasyncとawaitを使ってもできる。
この辺よく分からないまま、
業務でasyncとかawaitとかを使う→promiseについてちゃんと学ぶ
という完全に順番逆ーな感じで勉強した。

今となっては全体的な概要が理解できたつもりだが、結局asyncとawaitの方が記述が簡潔だし分かりやすいと感じたので、今回もasyncとawaitで記述する

async func1 = () => {
  await 処理1
  await 処理2
  await 処理3
  return ...
};

thenでつなげると長くなって見にくくなるけど、awaitだと1行で済むからとても見やすい

これで処理2は処理1が終わってから行われるし、処理3も然り
処理3が終わって初めてreturnされる

並行処理

処理1と処理2を並行で行って、どっちも終わってから処理3を行いたいという場合は、Promise.allを使うとよい

書き方はこんな感じ

async func1 = () => {
  await Promise.all([処理1, 処理2])
  await 処理3
  return ...
}

Promise.allの後に並行で処理したいものを配列?として[ ]の中に記載する。
[ ]の中の処理が全て終わった段階で次の処理に行くことになる。

ちなみにPromise.raceを使うと、処理1、処理2のうちどちらかが終わった段階で次の処理に行く。まさにrace。

mapメソッドの中での非同期処理の使い方

業務でハマったのはここ。
配列があって、その配列の中身をmapメソッドを使って一つずつ取り出して、そのたびにAPIを叩く処理を実装しようとした。

async func1 = () => {
  await array1.map(item =>
   API呼ぶ処理(item)
  )
  await array2.map(item =>
   API呼ぶ処理(item)
  )
  処理3
}

array1の全ての要素に対してAPI叩いた後、array2の全ての要素でAPIを叩くという操作をしたかったが、うまくいかない。

というのもmapメソッドの中身まではawaitしてくれないらしい。

じゃあmapメソッドの中でawaitさせてあげればいいんだなと思ってこうした。

async func1 = () => {
  await array1.map(item =>
   await API呼ぶ処理(item)
  )
  await array2.map(item =>
   await API呼ぶ処理(item)
  )
  処理3
}

すると、asyncないのにawait使ってんじゃねーよって怒られた。
async宣言する必要があるけどどこに宣言すればいいんだ?と思ったけど、
以下のようにすればいいらしい。

async func1 = () => {
  await array1.map(async item =>
   await API呼ぶ処理(item)
  )
  await array2.map(async item =>
   await API呼ぶ処理(item)
  )
  処理3
}

最初は「え、そこに入れるの!?」って思ったけど、よくよく考えたらasyncは関数の前に宣言するし、mapメソッドの中身はコールバック関数だし何も不思議じゃない。

当たり前のことだけど、ここでハマったおかげで非常に勉強になった。

ちなみに先ほどの2つの処理で使うAPIは同じもの、かつarray1とarray2の順不同だったので並行処理を使って以下のようにまとめた。

async func1 = () => {
  await Promise.all(
   [array1, array2].map(async item =>
    await API呼ぶ処理(item)
   )
  )
  処理3
}

これでAPIを叩く2つの処理を同時に走らせて、それらが全て終わったら処理3を行うという実装ができた。

非同期処理ちゃんと分かってなかったけど今回の件で色々調べられて非常に理解深まった。

そいえば、非同期処理って主にAPI叩く時に使ってるイメージだけど、それ以外で使うことってそんなにあるんだろうか?

参考

リンクはどれも分かりやすく非常に勉強になった。

async/await 入門(JavaScript) – Qiita

async/await地獄 – Qiita

Promiseについて0から勉強してみた – Qiita

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次