JavaScriptのトップレベルawait

概要: このチュートリアルでは、JavaScriptのトップレベルawaitとそのユースケースについて学びます。

JavaScriptのトップレベルawaitの概要

ES2020は、モジュールがasync関数のように動作することを許可するトップレベルawait機能を導入しました。トップレベルawaitモジュールをインポートするモジュールは、本体を評価する前にロードされるまで待機します。

トップレベルawait機能をよりよく理解するために、例を示します

この例では、index.htmlapp.mjsuser.mjsの3つのファイルを使用します

  • index.htmlapp.mjsファイルを使用します。
  • app.mjsuser.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)

メインフローは次のとおりです

JavaScript top-level await example

このフローでは

  • 最初に、app.mjsuser.mjsモジュールをインポートします。
  • 次に、user.mjsモジュールが実行され、API呼び出しを行います。
  • 第3に、2番目の手順がまだ進行中の間、app.mjsuser.mjsモジュールからインポートされたusersデータの使用を開始します。

ステップ2が完了していないため、users変数はundefinedでした。したがって、ページにエラーメッセージが表示されます。

回避策

問題を解決するには、Promiseuser.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モデルはusersPromiseを既定のエクスポートとしてエクスポートします。

app.mjspromiseusersuser.mjsファイルからインポートし、次のようにpromisethen()メソッドを呼び出します

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モジュールからpromiseusersをインポートします

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 モジュールが完了するのを待ちます。
このチュートリアルは役に立ちましたか?