🚀

JavaScript上の基本的な非同期操作のイディオムまとめてみた

2023/07/02に公開

概要

JavaScriptで非同期操作をするには、主にPromiseやasync/await(古いところだと、$.DefferedやXMLHttpRequestなんかも含まれるが)を使用する。

この中でよくつかわれる、いくつかのイディオムを紹介したいと思う。
マサカリ投げたい人はぜひ。これはおかしい!など、ご指摘いただけると嬉しい。

非同期関数の定義

まず、非同期用の関数の定義から、ここが現在スタンダードな、非同期関数の定義で、Promiseを使った実装は、特別なブラウザに対応が必要な場合以外はあまり見られなくなってきている。

async function hogeAsync()
}

下記から、いろいろな使用方法を紹介しようと思う。

非同期関数をただ呼び出す。

try {
  hogeAsync()
} catch (e) {
  console.error(e)
}

これは、ただ単純に非同期的に呼び出すだけのものであり
内部の処理が、後続の処理をブロックしない呼び出し方なので、準バッチ的な処理(pdf生成や動画処理)のような重めの処理を行う際には、これを使用することがある。
普段はあまり使わない。
上記のように書く場合は、APIなど時間のかかる操作が終わった後のUI操作は関数内部に作りこむことになる。

非同期関数を呼び出し待つ

try {
  await hogeAsync()
} catch (e) {
  console.error(e)
}

これが基本的な使い方になる。
async関数の呼び出しに対して、awaitの修飾子を付けることで
その処理が終わるまで待つことになる。

たとえば非同期関数にデータのフェッチを任せその間に何かを実行させたい場合は、下記のような実装になる。

try {
  const fetchSomethignPromise = fetchSomething()
  controlUi() // 何かしらのUI操作など
  await fetchSomethingPromise
} catch (e) {
  console.error(e)
}

非同期関数をまとめて実行する

下記では、待ちの発生する関数をまとめて実行することができる。
こうすることで複数のAPIを同時に呼び出すことができる。

try {
  await Promise.all([
    hogeAsync(),
    fugaAsync(),
    fooAsync()
  ])
} catch (e) {
  console.error(e)
}

もし、それぞれの関数が外部にリクエストを行うようなものだった場合、下記のように書くと
レスポンスが返ってきた時点で次のリクエストを行うことになるので、データ操作の順序に必然性がない場合、非効率な実装になってしまう。

try {
  await hogeAsync()
  await fugaAsync()
  await fooAsync()
} catch (e) {
  console.error(e)
}

複数の非同期実行のエラーハンドリングについて

複数の非同期実行をまとめて行う場合、下記のように例外を振り分けてエラーハンドリングを行うことになる。
使用しているフレームワークによっては、より上位のモジュールでcatchをすることになることもあるが、そこは使用箇所によって適宜読み替えてほしい。

try {
  await Promise.all([
    hogeAsync(),
    fugaAsync(),
    fooAsync()
  ])
} catch (AuthenticationError e) {
  console.error(e)
  // リトライ処理とか
} catch (ServerError e) {
  console.error(e)
  // エラー表示とか
} catch (NotFoundHogeError e) {
  // Hogeが見つかりません。とか
}

順序の依存関係が入れ子になる場合

順序関係が入れ子になる場合は非同期関数内にさらなる順序依存関係を書くことになる。
ある程度、DB上のデータをAPI設計の関係でフロント上でJOINしていかなければならない場合や、外部サービスの認証と自社サービスの認証を両方関係のある形で行わなければならない場合は、このように複雑化することもある。
特にフロント上JOINを行っていてあまりに複雑化してしまう場合は、GraphQLのような、柔軟性のより高いAPI設計を導入することを検討した方が良いだろう。

async function hogeAsync() {
  await fugaAsync()
  await Promise.all([
    fooAsync(),
    barAsync()
  ])
}

try {
  await hogeAsync()
  await hogeFugaAsync()
} catch (e) {
  console.error(e)
}

Discussion