async await に書き換えて、Promiseと 同期による例外の区別でハマった

  • 44
    Like
  • 3
    Comment

ハマった

元のコード

// async function の中
try {
  load().then(data => {
    console.log(data)
  }).catch(e => {
    // ...
  })
} catch (e) {
  // ... 例外処理
}

わかりやすく簡単にしている。実際にはもっと複雑なコードだった。Promise にすれば
try と catch を一本化して綺麗にできるやん!と思っていた。最初は。

書き換えた

// async function の中
try {
  const data = await load()
  console.log(data)
} catch (e) {
  // ... 例外処理
}

catch が一個減ってリファクタできたーと思っていた。確かに異なる例外処理のブロックが減ってしまっていたが、どうせ何かしらのデッドコードだろと思って消してしまった。

注: 意味的に変わってしまっているが、実際にはすごく複雑なコードで、大きな方のtryは別の例外をキャッチしていると思っていた…

何が起こったか

catch に来てた部分が、Promise の
UnhandledRejectionError になってしまっていた(のにしばらく気づかなかった)。

ここで、状況を述べておくと、 load() はライブラリが提供してた関数で、Promiseを返すことは知っていたが、実装には詳しくなかった。これがハマる理由になった。

挙動をちゃんと追うと、ここの load()同期的な例外 または Promise を返す という実装だった。期待していたのは Promise の resolve or reject だったので、ミスマッチがあったというわけ。

どう直したか

// async function の中
try {
  const loading = load()
  try {
    const data = await loading
    console.log(data)
  } catch (e) {
    // 非同期例外
  }
} catch (e) {
  // 同期例外
}

えーー!!ってコードになってしまった。でもこれが元々期待されてた挙動的に正しい…。

反省

  • async/await の中でも同期例外と非同期例外を区別する
  • 自分がPromiseの関数を実装するときは Promiseのインターフェースに押し込めるようしたいと思った
  • 例外パターンに対するテストは常に書こう

以上、これで2日分ほどハマった現場よりの報告でした。