概要: このチュートリアルでは、JavaScriptのトップレベルawaitとそのユースケースについて学びます。
JavaScriptのトップレベルawaitの概要
ES2020は、モジュールがasync関数のように動作することを許可するトップレベルawait機能を導入しました。トップレベルawaitモジュールをインポートするモジュールは、本体を評価する前にロードされるまで待機します。
トップレベルawait機能をよりよく理解するために、例を示します
この例では、index.html、app.mjs、user.mjsの3つのファイルを使用します
index.htmlはapp.mjsファイルを使用します。app.mjsはuser.mjsファイルをインポートします。user.mjsは、URLエンドポイントhttps://jsonplaceholder.typicode.com/usersを持つAPIから、ユーザーデータをJSON形式でフェッチします
app.mjsモジュールを使用するインデックスファイルを示します
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript Top-Level Await Demo</title>
</head>
<body>
<div class="container"></div>
<script type="module" src="app.mjs"></script>
</body>
</html>Code language: HTML, XML (xml)user.mjsファイルを示します
let users;
(async () => {
const url = 'https://jsonplaceholder.typicode.com/users';
const response = await fetch(url);
users = await response.json();
})();
export { users };Code language: JavaScript (javascript)user.mjsモジュールはfetch APIを使用して、ユーザーをJSON形式でAPIから取得し、エクスポートします。
awaitキーワードは(ES2020より前の)async関数内でのみ使用できるため、API呼び出しを即時呼び出し非同期関数式(IIAFE)内にラップする必要があります。
app.mjsモジュールを示します
import { users } from './user.mjs';
function render(users) {
if (!users) {
throw 'The user list is not available';
}
const list = users
.map((user) => {
return `<li> ${user.name}(<a href="email:${user.email}">${user.email}</a>)</li>`;
})
.join('');
return `<ol>${list}</ol>`;
}
const container = document.querySelector('.container');
try {
container.innerHTML = render(users);
} catch (e) {
container.innerHTML = e;
}
Code language: JavaScript (javascript)仕組み
最初に、user.mjsモジュールからusersをインポートします
import { users } from './user.mjs';Code language: JavaScript (javascript)次に、ユーザーリストをHTML形式の順序付きリストにレンダリングするrender()関数を作成します
function render(users) {
if (!users) {
throw 'The user list is not available.';
}
const list = users
.map((user) => {
return `<li> ${user.name}(<a href="email:${user.email}">${user.email}</a>)</li>`;
})
.join('');
return `<ol>${list}</ol>`;
}Code language: JavaScript (javascript)第3に、クラス.containerを持つHTML要素にユーザーリストを追加します
const container = document.querySelector('.container');
try {
container.innerHTML = render(users);
} catch (e) {
container.innerHTML = e;
}Code language: JavaScript (javascript)index.htmlを開くと、次のメッセージが表示されます
The user list is not available.Code language: PHP (php)メインフローは次のとおりです
このフローでは
- 最初に、
app.mjsはuser.mjsモジュールをインポートします。 - 次に、
user.mjsモジュールが実行され、API呼び出しを行います。 - 第3に、2番目の手順がまだ進行中の間、
app.mjsはuser.mjsモジュールからインポートされたusersデータの使用を開始します。
ステップ2が完了していないため、users変数はundefinedでした。したがって、ページにエラーメッセージが表示されます。
回避策
問題を解決するには、Promiseをuser.mjsモジュールからエクスポートし、その結果を使用する前にAPI呼び出しが完了するまで待つことができます。
user.mjsモジュールの新しいバージョンを示します
let users;
export default (async () => {
const url = 'https://jsonplaceholder.typicode.com/users';
const response = await fetch(url);
users = await response.json();
})();
export { users };Code language: JavaScript (javascript)この新しいバージョンでは、user.mjsモデルはusersとPromiseを既定のエクスポートとしてエクスポートします。
app.mjsはpromiseとusersをuser.mjsファイルからインポートし、次のようにpromiseのthen()メソッドを呼び出します
import promise, { users } from './user.mjs';
function render(users) {
if (!users) {
throw 'The user list is not available.';
}
let list = users
.map((user) => {
return `<li> ${user.name}(<a href="email:${user.email}">${user.email}</a>)</li>`;
})
.join(' ');
return `<ol>${list}</ol>`;
}
promise.then(() => {
let container = document.querySelector('.container');
try {
container.innerHTML = render(users);
} catch (error) {
container.innerHTML = error;
}
});
Code language: JavaScript (javascript)仕組み
最初に、user.mjsモジュールからpromiseとusersをインポートします
import promise, { users } from './user.mjs';Code language: JavaScript (javascript)次に、プロミスthen()メソッドを呼び出し、その結果を使用するためにAPI呼び出しが完了するまで待ちます
promise.then(() => {
let container = document.querySelector('.container');
try {
container.innerHTML = render(users);
} catch (error) {
container.innerHTML = error;
}
});Code language: JavaScript (javascript)これで、index.htmlを開くと、ユーザーのリストが表示されます。ただし、モジュールを使用する場合、待機する正しいプロミスを知る必要があります。
この回避策でES2022はこの問題を解決するためにトップレベルのawaitモジュールを導入しました。
トップレベルawaitの使用
最初に、user.mjsを以下のように変更します
const url = 'https://jsonplaceholder.typicode.com/users';
const response = await fetch(url);
let users = await response.json();
export { users };Code language: JavaScript (javascript)このモジュールでは、ステートメントをasync関数内に配置せずにawaitキーワードを使用できます。
次に、user.mjs モジュールからユーザーをインポートして利用します
import { users } from './user.mjs';
function render(users) {
if (!users) {
throw 'The user list is not available.';
}
let list = users
.map((user) => {
return `<li> ${user.name}(<a href="email:${user.email}">${user.email}</a>)</li>`;
})
.join(' ');
return `<ol>${list}</ol>`;
}
let container = document.querySelector('.container');
try {
container.innerHTML = render(users);
} catch (error) {
container.innerHTML = error;
}
Code language: JavaScript (javascript)この場合、app.mjs モジュールは user.mjs モジュールの完了を待ってからその本体を実行します。
JavaScript トップレベル await のユースケース
いつトップレベル await を使用するのでしょうか。以下にいくつかのユースケースを示します。
動的依存関係パジング
const words = await import(`/i18n/${navigator.language}`);Code language: JavaScript (javascript)この例では、トップレベル await によりモジュールは実行時に依存関係を決定するための値を使用できます。これは次のシナリオで役立ちます。
- インターナショナル化 (i18n)
- 開発/本番環境の分割。
依存関係のフォールバック
この場合、トップレベル await を使用してサーバー (cdn1) からモジュールをロードできます。失敗した場合、バックアップサーバー (cdn2) からロードできます。
let module;
try {
module = await import('https://cdn1.com/module');
} catch {
module = await import('https://cdn2.com/module');
}Code language: JavaScript (javascript)まとめ
- トップレベル await モジュールは
async関数のように動作します。 - モジュールがトップレベル await モジュールをインポートすると、その本体を評価する前にトップレベル await モジュールが完了するのを待ちます。