トップレベルawait

公開日 · タグ:ECMAScript

トップレベル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を使用した場合の動作を以下に示します。

  1. 現在のモジュールの実行は、待機中のPromiseが解決されるまで延期されます。
  2. 親モジュールの実行は、awaitを呼び出した子モジュールとそのすべての兄弟がバインディングをエクスポートするまで延期されます。
  3. 兄弟モジュールと親モジュールの兄弟は、グラフにサイクルやその他のawaitされたPromiseがない限り、同じ同期的な順序で実行を継続できます。
  4. awaitを呼び出したモジュールは、awaitされたPromiseが解決された後、実行を再開します。
  5. 親モジュールと後続のツリーは、他のawaitされたPromiseがない限り、同期的な順序で実行を継続します。

DevToolsでは既に動作していませんか? #

実際、動作します!Chrome DevToolsNode.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のサポート #