概要: このチュートリアルでは、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 モジュールが完了するのを待ちます。