トップレベルawait
を使用すると、開発者は非同期関数以外でawait
キーワードを使用できます。これは大きな非同期関数のように動作し、それをimport
する他のモジュールは、本体の評価を開始する前に待機します。
以前の動作 #
async
/await
が最初に導入されたとき、非同期関数以外でawait
を使用しようとすると、SyntaxError
が発生しました。多くの開発者は、すぐに実行される非同期関数式を使用して、この機能にアクセスしていました。
await Promise.resolve(console.log('🎉'));
// → SyntaxError: await is only valid in async function
(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
を使用した場合の動作を以下に示します。
- 現在のモジュールの実行は、待機中の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による有名なgistをご覧になったことがあるかもしれません。このgistでは、当初、トップレベルawait
に関するいくつかの懸念が概説されており、JavaScript言語でこの機能を実装しないように促していました。具体的な懸念事項としては、
- トップレベル
await
が実行をブロックする可能性がある。 - トップレベル
await
がリソースのフェッチをブロックする可能性がある。 - CommonJSモジュールのための明確な相互運用性ストーリーがない。
提案のステージ3バージョンでは、これらの問題が直接解決されています。
- 兄弟が実行できるため、明確なブロックはありません。
- トップレベル
await
は、モジュールグラフの実行フェーズで発生します。この時点で、すべてのリソースは既にフェッチおよびリンクされています。リソースのフェッチをブロックするリスクはありません。 - トップレベル
await
はモジュールに限定されています。スクリプトやCommonJSモジュールは明示的にサポートされていません。
新しい言語機能には、常に予期しない動作のリスクが伴います。たとえば、トップレベルawait
では、循環的なモジュール依存関係によってデッドロックが発生する可能性があります。
トップレベルawait
がない場合、JavaScript開発者は、await
にアクセスするために、しばしば非同期すぐに実行される関数式を使用していました。残念ながら、このパターンは、グラフの実行の決定性とアプリケーションの静的解析可能性を低下させます。これらの理由から、トップレベルawait
がないことの方が、この機能によって導入される危険性よりもリスクが高いと見なされていました。
トップレベルawait
のサポート #
- Chrome: バージョン89以降でサポート
- Firefox: サポートされていません
- Safari: バージョン15以降でサポート
- Node.js: バージョン14以降でサポート
- Babel: サポートされていません