ほりひログ

所属組織の製品 (Azure とか) に関連する内容が多めだけど、個人の見解であって、所属組織を代表する公式情報ではないです。

2025年になってもasync/awaitの理解が足りてない

async / await が最初に実装されたのがChrome 55で、これがリリースされた2016年*1だそうで、もうすぐ10年がたとうとしている。
なのに、まだasync / await への理解不足が止まらない。

この理解不足のせいで、Chrome ExtensionのwebNavigation APIを使った実装でバグった。

developer.chrome.com

webNavigation APIは、ドキュメントによるとイベントの発火順序が決まっている。

なので、

chrome.webNavigation.onBeforeNavigate.addListener((details) => {
  console.log('処理その1');
});

chrome.webNavigation.onCommitted.addListener((details) => {
  console.log('処理その2');
});

というコードを書いたらコンソールには必ず、

処理その1
処理その2

という順序で出力される。
これがasync/awaitがないシンプルなパターン。

問題になるのは、先に発火するonBeforeNavigateのリスナーにasyncな関数を使ったケース。

chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
  console.log('処理その1-a');
  await ...
  console.log('処理その1-b');
});

chrome.webNavigation.onCommitted.addListener((details) => {
  console.log('処理その2');
});

この場合のコンソールへの出力は、一見onBeforeNavigateのリスナーでは「処理その1-a」と「処理その1-b」を一気に実行して、

処理その1-a
処理その1-b
処理その2

と出力しそうに見える。

でも実際はawait以降は非同期処理なので、awaitを挟んで前半と後半は別のイベントループで処理される。

前半のイベントループの処理のすぐ後に、別のイベントループで処理される後半の処理が来るなら、上のような出力になり、
onCommitedイベントの発火が割り込むと、

処理その1-a
処理その2
処理その1-b

になる。

これはどちらになるかわからない。

冒頭のChrome ExtensionのwebNavigation APIを使った実装でのバグは、これがまさに起こっていた。

onBeforeNavigateのリスナー内でawaitしてたのに、そのawait以降の処理の後にonCommittedのイベント発火の前に実行されることを前提にして実装していたことが原因だった。

恥ずかしい限り。

けど一つ学んだ。

async awaitで非同期処理を同期処理の様に記述できて、読みやすくなる。 けど、実際の実行時は awaitの前後で処理が分断されていて、awaitの次に行く前に別のイベントループ処理が始まってしまうことは頭に入れておかねば。

忘れそうだけど。