トップレベルの`await`
トップレベルのawaitは、開発者が非同期関数の外部でawaitキーワードを使用できるようにします。それは、他のモジュールがそれらをimportする際に、モジュールのボディを評価する前に待機する大きな非同期関数のように動作します。
従来の動作
async/awaitが初めて導入されると、非同期関数の外部でawaitを使用しようとするとSyntaxErrorが発生しました。多くの開発者は即時実行される非同期関数式を利用することで、この機能にアクセスしました。
await Promise.resolve(console.log('🎉'));
// → SyntaxError: awaitは非同期関数内でのみ有効です
(async function() {
await Promise.resolve(console.log('🎉'));
// → 🎉
}());
新しい動作
トップレベルのawaitが導入されることで、上記のコードはモジュール内で期待通りに動作するようになります。
await Promise.resolve(console.log('🎉'));
// → 🎉
注: トップレベルのawaitはモジュールのトップレベルでのみ機能します。従来のスクリプトや非同期でない関数へのサポートはありません。
使用例
これらの使用例は仕様提案リポジトリから引用されています。
動的依存パス
const strings = await import(`/i18n/${navigator.language}`);
これにより、モジュールは実行時の値を使用して依存関係を決定することができます。これは開発/本番モードの分割、国際化、環境の分割などに便利です。
資源の初期化
const connection = await dbConnector();
これはモジュールがリソースを表現することを可能にし、モジュールが使用できない場合にエラーを生成することも可能にします。
依存関係のフォールバック
以下の例では、CDN AからJavaScriptライブラリを読み込み、それが失敗した場合はCDN Bにフォールバックします。
let jQuery;
try {
jQuery = await import('https://cdn-a.example.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.example.com/jQuery');
}
モジュールの実行順序
トップレベルのawaitを導入することでJavaScriptにおける最大の変化の1つは、グラフ内でのモジュールの実行順序です。JavaScriptエンジンはモジュールを後順トラバーサルで実行します: モジュールグラフの左端の部分木から始まり、モジュールを評価し、それらのバインディングをエクスポートし、その兄弟を実行し、その後に親を実行します。このアルゴリズムは再帰的に実行され、モジュールグラフのルートを実行するまで続きます。
トップレベルのawait以前では、この順序は常に同期的かつ決定的でした: コードを複数回実行しても、グラフは同じ順序で実行される保証がありました。トップレベルのawaitが導入されると、その保証は引き続き存在しますが、トップレベルのawaitを使用しない場合に限ります。
モジュールでトップレベルのawaitを使用すると、以下のようになります。
- 現在のモジュールの実行は、
awaitされたPromiseが解決されるまで遅延されます。 - 子モジュールが
awaitを呼び出し、その兄弟がバインディングをエクスポートするまで、親モジュールの実行が遅延されます。 - 兄弟モジュール、及び親モジュールの兄弟は、グラフ内で循環や他の
awaitされたPromiseがない場合に同期的な順序で実行を続けます。 awaitを呼び出したモジュールは、awaitされたPromiseが解決された後に実行を再開します。- 親モジュールおよびその後のツリーは、他の
awaitされたPromiseがない場合は同期的な順序で実行を続けます。
これはすでにDevToolsで動作していませんか?
実際に動作しています!Chrome DevTools、Node.js、Safari Web InspectorのREPLでは、既にトップレベルのawaitをサポートしています。ただし、この機能は標準ではなく、REPLに限定されていました!これは言語仕様の一部であるトップレベルのawait提案とは異なり、モジュールにのみ適用されます。本番コードでトップレベルのawaitを使用して、それが仕様提案のセマンティクスに完全に一致するかどうかをテストする場合は、DevToolsやNode.js REPLではなく、実際のアプリでテストすることを確実にしてください!
トップレベルのawaitは問題を引き起こす可能性があるのでは?
おそらく、Rich Harrisが作成した悪名高いジストを目にしたことがあるでしょう。このジストでは、トップレベルのawaitに関する懸念がいくつか初めて提起され、JavaScript言語にその機能を実装しないよう促されました。具体的な懸念事項としては以下が挙げられます:
- トップレベルの
awaitが実行をブロックする可能性がある。 - トップレベルの
awaitがリソースのフェッチをブロックする可能性がある。 - CommonJSモジュールの明確な相互運用性がない。
提案のステージ3バージョンはこれらの問題に直接対処しています:
- 同じ階層の他のスクリプトは実行を続けられるため、決定的なブロックは発生しません。
- トップレベルの
awaitはモジュールグラフの実行フェーズ中に発生します。この時点で、すべてのリソースはすでにフェッチおよびリンクされています。リソースのフェッチをブロックするリスクはありません。 - トップレベルの
awaitはモジュールに限定されています。スクリプトやCommonJSモジュールへのサポートは明確にありません。
新しい言語機能には常に予期しない動作のリスクがあります。たとえばトップレベルのawaitの場合、循環モジュール依存がデッドロックを引き起こす可能性があります。
トップレベルのawaitがない場合、多くのJavaScript開発者はawaitにアクセスするために非同期即時実行関数式を使用していました。しかし、このパターンはグラフの実行やアプリケーションの静的解析可能性を低下させます。このような理由から、トップレベルのawaitの欠如は、この機能によって導入される危険性よりも大きなリスクと見なされていました。