Как использовать top-level await в JavaScript

В этом руководстве вы узнаете о модуле top-level await в JavaScript и вариантах его использования.

ES2020 представил функцию await верхнего уровня, которая позволяет модулю вести себя как async функция. Модуль, который импортирует модуль верхнего уровня, будет ждать его загрузки, прежде чем оценивать его тело.

Чтобы лучше понять функцию top-level await, мы возьмем пример:

top-level await в JavaScript

В этом примере у нас будет три файла: index.html, app.mjs и user.mjs :

  • В index.html используется файл app.mjs.
  • app.mjs импортирует файл user.mjs.
  • user.mjs извлекает пользовательские данные в формате JSON из API с конечной точкой URL https://jsonplaceholder.typicode.com/users.

Вот индексный файл, в котором используется модуль 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>

Ниже показан файл user.mjs :

let users;

(async () => {
  const url = 'https://jsonplaceholder.typicode.com/users';
  const response = await fetch(url);
  users = await response.json();
})();

export { users };

Модуль user.mjs использует API выборки, чтобы получить пользователей в формате JSON из API и экспортировать их.

Поскольку мы можем использовать ключевое слово await только внутри async функции (до ES2020), нам нужно обернуть вызов 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;
}

Как это работает:

  • Сначала импортируйте users из модуля user.mjs :
import { users } from './user.mjs';
  • Затем создайте функцию render(), которая преобразует список пользователей в упорядоченный список в формате HTML:
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>`;
}
  • В-третьих, добавьте список пользователей в элемент HTML с классом .container :
const container = document.querySelector('.container');
try {
  container.innerHTML = render(users);
} catch (e) {
  container.innerHTML = e;
}

Если вы откроете index.html, вы увидите следующее сообщение:

The user list is not available.

Ниже показан основной поток:

Пример ожидания JavaScript верхнего уровня

В этом потоке:

  • Во-первых, app.mjs импортирует модуль user.mjs.
  • Во-вторых, модуль user.mjs выполняется и выполняет вызов API.
  • В-третьих, пока выполняется второй этап, app.mjs начинает использовать данные users, импортированные из модуля user.mjs.

Поскольку шаг 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 };

В этой новой версии модель user.mjs экспортирует users и Promise в качестве экспорта по умолчанию.

В app.mjs импортирует promise и users из файла user.mjs и затем вызывает метод then() promise следующим образом:

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;
  }
});

Как это работает:

  • Сначала импортируйте promise и users из модуля user.mjs :
import promise, { users } from './user.mjs';
  • Во-вторых, вызовите метод then() обещания и дождитесь завершения вызова API, чтобы использовать его результаты:
promise.then(() => {
  let container = document.querySelector('.container');
  try {
    container.innerHTML = render(users);
  } catch (error) {
    container.innerHTML = error;
  }
});

Теперь, если вы откроете index.html, вы увидите список пользователей. Однако вам нужно знать правильное обещание ждать его при использовании модуля.

ES2022 представил в этом обходном пути модуль ожидания верхнего уровня для решения этой проблемы.

Использование верхнего уровня

Сначала измените user.mjs на следующее:

const url = 'https://jsonplaceholder.typicode.com/users';
const response = await fetch(url);
let users = await response.json();

export { users };

В этом модуле вы можете использовать ключевое слово await без помещения оператора в async функцию.

Во-вторых, импортируйте пользователей из модуля 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;
}

В этом случае модуль app.mjs будет ждать завершения работы модуля user.mjs, прежде чем выполнять его тело.

Варианты использования

Когда вы используете top-level await? Вот несколько вариантов.

Динамический путь к зависимостям

const words = await import(`/i18n/${navigator.language}`);

В этом примере top-level await позволяет модулям использовать значения времени выполнения для определения зависимостей, что полезно для следующих сценариев:

  • Интернационализация (i18n)
  • Разделение среды разработки/производства.

Запасной вариант зависимости

В этом случае вы можете использовать await верхнего уровня для загрузки модуля с сервера(cdn1). А если не получится, то можно загрузить с резервного сервера(cdn2):

let module;
try {
  module = await import('https://cdn1.com/module');
} catch {
  module = await import('https://cdn2.com/module');
}

Заключение

  • Модуль Top-level await действует как async функция.
  • Когда модуль импортирует модуль Top-level await в Джаваскрипт, он ожидает его завершения, прежде чем оценивать его тело.
Рейтинг
( Пока оценок нет )
Александр Русаков / автор статьи
Программист, разработчик, 12 лет опыта работы в крупных компаниях. Быстро освоил typescript, делюсь своими знаниями на страницах этого сайта.
Загрузка ...
JavaScript и TypeScript