В этом руководстве вы узнаете о модуле top-level await в JavaScript и вариантах его использования.
ES2020 представил функцию await верхнего уровня, которая позволяет модулю вести себя как async
функция. Модуль, который импортирует модуль верхнего уровня, будет ждать его загрузки, прежде чем оценивать его тело.
Чтобы лучше понять функцию top-level await, мы возьмем пример:
В этом примере у нас будет три файла: 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.
Ниже показан основной поток:
В этом потоке:
- Во-первых,
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 в Джаваскрипт, он ожидает его завершения, прежде чем оценивать его тело.