JavaScriptの記述方法を変える可能性のある新機能は、現代のWebの進化を形作っている非常に柔軟で強力な言語です。JavaScriptがWeb開発で非常に支配的である主な理由の1つは、その迅速な開発と継続的な改善です。
JavaScriptを改善するための1つの提案は、提案です。「トップレベル待機」と呼ばれます(トップレベル待機、「グローバル」待機)。この提案の目的は、ESモジュールを非同期関数のようなものに変えることです。これにより、モジュールはすぐに使用できるリソースを取得し、モジュールがそれらをインポートするのをブロックできます。待機中のリソースをインポートするモジュールは、リソースが受信されて使用できるように準備された後でのみ、コードの実行を実行できます。
この提案は現在3段階で検討されているため、この機能はまだ本番環境では使用できません。ただし、近い将来、確実に実装されることは間違いありません。
これについては心配しないでください。読み続けます。名前付き機能を今すぐ使用する方法を紹介します。
通常の待機の何が問題になっていますか?
非同期関数の外部でawaitキーワードを使用しようとすると、構文エラーが発生します。これを回避するために、開発者は即時呼び出し関数式(IIFE)を使用します。
await Promise.resolve(console.log("️")); //
(async () => {
await Promise.resolve(console.log("️"))
})();
指定された問題とその解決策は、氷山の一角にすぎません。
ES6モジュールを使用する場合、値をエクスポートおよびインポートする多くのインスタンスを処理する傾向があります。例を考えてみましょう:
// library.js
export const sqrt = Math.sqrt;
export const square = (x) => x * x;
export const diagonal = (x, y) => sqrt((square(x) + square(y)));
// middleware.js
import { square, diagonal } from "./library.js";
console.log("From Middleware");
let squareOutput;
let diagonalOutput;
const delay = (ms) => new Promise((resolve) => {
const timer = setTimeout(() => {
resolve(console.log("️"));
clearTimeout(timer);
}, ms);
});
// IIFE
(async () => {
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
})();
export { squareOutput, diagonalOutput };
上記の例では、library.jsとmiddleware.jsの間で変数をエクスポートおよびインポートしています。ファイルには任意の名前を付けることができます。
delay関数は、遅延後に解決するpromiseを返します。この関数は非同期であるため、IIFE内で「await」キーワードを使用して、完了するのを「待機」します。実際のアプリケーションでは、「遅延」関数の代わりに、フェッチ(データの受信要求)またはその他の非同期タスクの呼び出しがあります。 promiseを解決した後、変数に値を割り当てます。これは、promiseが解決されるまで変数が未定義になることを意味します。
コードの最後で、他のコードで使用できるように変数をエクスポートします。
これらの変数がインポートされて使用されるコードを見てみましょう。
// main.js
import { squareOutput, diagonalOutput } from "./middleware.js";
console.log(squareOutput); // undefined
console.log(diagonalOutput); // undefined
console.log("From Main");
const timer1 = setTimeout(() => {
console.log(squareOutput);
clearTimeout(timer1);
}, 2000); // 169
const timer2 = setTimeout(() => {
console.log(diagonalOutput);
clearTimeout(timer2);
}, 2000); // 13
このコードを実行すると、最初の2つのケースでは未定義になり、3番目と4番目のケースではそれぞれ169と13になります。なぜそれが起こるのですか?
これは、非同期関数を実行する前に、main.jsのmiddleware.jsからエクスポートされた変数の値を取得しようとしているためです。解決待ちの約束があることを覚えていますか?
この問題を解決するには、変数を使用する準備ができていることをインポートモジュールに通知する必要があります。
回避策
この問題を解決するには、少なくとも2つの方法があります。
1.初期化のためのエクスポートの約束
まず、IIFEをエクスポートできます。asyncキーワードはメソッドを非同期にします。そのようなメソッドは、常にpromiseを返します。これが、以下の例で、非同期IIFEがpromiseを返す理由です。
// middleware.js
import { square, diagonal } from "./library.js";
console.log("From Middleware");
let squareOutput;
let diagonalOutput;
const delay = (ms) => new Promise((resolve) => {
const timer = setTimeout(() => {
resolve(console.log("️"));
clearTimeout(timer);
}, ms);
});
// , ,
export default (async () => {
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
})();
export { squareOutput, diagonalOutput };
main.jsでエクスポートされた変数にアクセスしている間、IIFEが実行されるのを待つことができます。
// main.js
import promise, { squareOutput, diagonalOutput } from "./middleware.js";
promise.then(() => {
console.log(squareOutput); // 169
console.log(diagonalOutput); // 169
console.log("From Main");
});
const timer1 = setTimeout(() => {
console.log(squareOutput);
clearTimeout(timer1);
}, 2000); // 169
const timer2 = setTimeout(() => {
console.log(diagonalOutput);
clearTimeout(timer2);
}, 2000); // 13
このスニペットが問題を解決するという事実にもかかわらず、それは他の問題につながります。
- 指定されたテンプレートを使用する場合は、目的の約束を探す必要があります
- 別のモジュールでも変数「squareOutput」と「diagonalOutput」を使用する場合は、IIFEが再エクスポートされていることを確認する必要があります
別の方法もあります。
2.エクスポートされた変数を使用したIIFEpromiseの解決
この場合、変数を個別にエクスポートする代わりに、非同期IIFEから変数を返します。これにより、「main.js」ファイルは、Promiseが解決してその値を取得するのを待つだけで済みます。
// middleware.js
import { square, diagonal } from "./library.js";
console.log("From Middleware");
let squareOutput;
let diagonalOutput;
const delay = (ms) => new Promise((resolve) => {
const timer = setTimeout(() => {
resolve(console.log("️"));
clearTimeout(timer);
}, ms);
});
//
export default (async () => {
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
return { squareOutput, diagonalOutput };
})();
// main.js
import promise from "./middleware.js";
promise.then(({ squareOutput, diagonalOutput }) => {
console.log(squareOutput); // 169
console.log(diagonalOutput); // 169
console.log("From Main");
});
const timer1 = setTimeout(() => {
console.log(squareOutput);
clearTimeout(timer1);
}, 2000); // 169
const timer2 = setTimeout(() => {
console.log(diagonalOutput);
clearTimeout(timer2);
}, 2000); // 13
ただし、このソリューションにはいくつかの欠点もあります。
提案によると、「このパターンには、関連するリソースソースをより動的なテンプレートに大幅にリファクタリングし、動的モジュールを許可するためにモジュール本体の大部分を.then()コールバックに配置する必要があるという重大な欠点があります。これは、ES2015モジュールと比較して、静的分析機能、テスト容易性、人間工学などの点で大幅な回帰を表しています。」
「グローバル」はどのようにこの問題を解決するのを待っていますか?
トップレベルの待機により、モジュラーシステムが約束の解決とそれらの相互作用を処理できます。
// middleware.js
import { square, diagonal } from "./library.js";
console.log("From Middleware");
let squareOutput;
let diagonalOutput;
const delay = (ms) => new Promise((resolve) => {
const timer = setTimeout(() => {
resolve(console.log("️"));
clearTimeout(timer);
}, ms);
});
// "" await
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
export { squareOutput, diagonalOutput };
// main.js
import { squareOutput, diagonalOutput } from "./middleware.js";
console.log(squareOutput); // 169
console.log(diagonalOutput); // 13
console.log("From Main");
const timer1 = setTimeout(() => {
console.log(squareOutput);
clearTimeout(timer1);
}, 2000); // 169
const timer2 = setTimeout(() => {
console.log(diagonalOutput);
clearTimeout(timer2);
}, 2000); // 13
middleware.jsのpromiseが解決されるまで、main.jsのステートメントは実行されません。これは、回避策よりもはるかにクリーンなソリューションです。
ノート
グローバル待機は、ESモジュールでのみ機能します。使用する依存関係は明示的に指定する必要があります。以下の提案リポジトリの例は、これをよく示しています。
// x.mjs
console.log("X1");
await new Promise(r => setTimeout(r, 1000));
console.log("X2");
// y.mjs
console.log("Y");
// z.mjs
import "./x.mjs";
import "./y.mjs";
// X1
// Y
// X2
xとyは別々のモジュールであり、互いに関連していないため、このスニペットは、ご想像のとおり、X1、X2、Yをコンソールに表示しません。問題の機能をよりよく理解するために、提案のFAQセクション
を調べることを強くお勧めします。
実装
V8
この機能は今すぐテストできます。
これを行うには、Chromeがマシン上にあるディレクトリに移動します。すべてのブラウザタブが閉じていることを確認してください。ターミナルを開き、次のコマンドを入力します。
chrome.exe --js-flags="--harmony-top-level-await"
Node.jsでこの機能を試すこともできます。詳細については、このガイドをお読みください。
ESモジュール
値が「module」の「script」タグに「type」属性を必ず追加してください。
<script type="module" src="./index.js"></script>
通常のスクリプトとは異なり、ES6モジュールは共有オリジン(シングルソース)(SOP)およびリソース共有(CORS)ポリシーに従うことに注意してください。したがって、サーバー上でそれらを操作することをお勧めします。
ユースケース
提案に よると、「グローバル」の使用例は次のとおりです。
動的依存パス
const strings = await import(`/i18n/${navigator.language}`);
これにより、モジュールはランタイム値を使用して依存関係パスを計算でき、開発/本番コードの分離、国際化、ランタイム(ブラウザ、Node.js)に基づくコードの分離などに役立ちます。
リソースの初期化
const connection = await dbConnector()
これは、モジュールがすぐに使用できるリソースを取得し、モジュールが使用できないときに例外をスローするのに役立ちます。このアプローチは、以下に示すように、セーフティネットとして使用できます。
フォールバックオプション
以下の例は、「グローバル」待機を使用して、フォールバック実装で依存関係をロードする方法を示しています。CDN Aからのインポートが失敗した場合、CDNBからのインポートが実行されます。
let jQuery;
try {
jQuery = await import('https://cdn-a.example.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.example.com/jQuery');
}
批判
リッチハリスは、トップレベルの待望の批判のリストをまとめました。これには次のものが含まれます。
- 「グローバル」待機はコードの実行をブロックできます
- 「グローバル」待機は、リソースの取得をブロックできます
- CommonJSモジュールのサポートの欠如
これらのコメントへの回答は、FAQ提案に記載されています。
- 子ノード(モジュール)には実行機能があるため、最終的にコードブロッキングは発生しません。
- 「グローバル」待機は、モジュールグラフの実行フェーズで使用されます。この段階では、すべてのリソースが受信およびリンクされているため、リソースの取得がブロックされるリスクはありません。
- トップレベルの待機はES6モジュールに制限されています。CommonJSモジュールのサポートは、通常のスクリプトのように、当初は計画されていませんでした
繰り返しになりますが、提案のFAQを読むことを強くお勧めします。
問題の提案の本質をわかりやすく説明できたと思います。この機会を利用しますか?コメントであなたの意見を共有してください。