Skip to main content

Що таке "top-level await"?

Що було раніше

До top-level await використовувати await можна було лише всередині async-функції:

javascript
// Помилка раніше const data = await fetch('/api/data'); // Працює async function main() { const data = await fetch('/api/data'); console.log(await data.json()); } main();

Якщо потрібно було дочекатися чогось на початку модуля, доводилося створювати функцію-обгортку (init(), bootstrap() тощо).


Що змінилося

З top-level await тепер можна писати:

javascript
// module.mjs або type: "module" const response = await fetch('https://api.example.com/user'); const user = await response.json(); console.log('Користувач:', user.name);

Код призупиняє завантаження модуля, поки проміс не виконається. Це робить модуль асинхронним "за своєю природою".


Де можна використовувати

Лише в ES-модулях (ESM) - тобто у файлах:

  • з розширенням .mjs, або
  • у .js, якщо в package.json вказано "type": "module", або
  • у <script type="module"> в браузері.

У CommonJS (Node.js require) - не можна.


Як це працює під капотом

Коли збирач або рантайм зустрічає await на верхньому рівні модуля:

  1. Модуль стає асинхронним.
  2. Його виконання призупиняється, поки не виконається await.
  3. Інші модулі, які імпортують цей модуль, теж чекають, поки він завершить виконання (через ланцюжок залежностей).

Приклад 1 - імпорт з очікуванням

javascript
// user.mjs export const user = await fetch('/api/user').then(r => r.json());
javascript
// main.mjs import { user } from './user.mjs'; console.log("Ім'я користувача:", user.name);

Тут main.mjs не почне виконуватися, поки user.mjs не виконає свій await.


Приклад 2 - динамічна ініціалізація

javascript
// config.mjs const env = await fetch('/env.json').then(r => r.json()); export const API_URL = env.production ? 'https://api.prod' : 'https://api.dev';

Модуль config тепер можна імпортувати як звичайний, а він сам асинхронно підтягне конфіг.


Приклад 3 - top-level await з динамічним імпортом

javascript
// main.mjs const lang = navigator.language.startsWith('fr') ? 'fr' : 'en'; const messages = await import(`./messages.${lang}.js`); console.log(messages.default.hello);

Дозволяє динамічно імпортувати модуль, використовуючи асинхронний синтаксис без зайвих обгорток.


Взаємодія з іншими модулями

Якщо модуль A використовує await на верхньому рівні, а модуль B імпортує A, то B теж чекає, поки A завершиться.

javascript
// a.mjs console.log('A start'); await new Promise(r => setTimeout(r, 1000)); console.log('A done'); // b.mjs import './a.mjs'; console.log('B start');

Вивід:

javascript
A start A done B start

Переваги

Спрощує ініціалізацію модулів (немає async init() навколо). Усуває "піраміду імпортів" для асинхронних залежностей. Дозволяє гнучко робити асинхронні імпорти (await import()).


Потенційні мінуси

Блокування ланцюжка модулів - якщо один модуль "висить" на довгому await, усе дерево залежностей чекає на його завершення.

Не можна використовувати в CommonJS (лише ESM).

Може вплинути на час старту застосунку (особливо в Node JS).


Приклад у Node.js

javascript
// package.json { "type": "module" }
javascript
// index.js import fs from 'fs/promises'; const config = JSON.parse(await fs.readFile('./config.json', 'utf-8')); console.log('Config:', config);

Працює, тому що Node JS підтримує top-level await в ESM-режимі.


Підсумок

ОсобливістьОпис
Що робитьДозволяє використовувати await поза async-функціями
Де працюєЛише в ES-модулях
Що повертаєМодуль стає асинхронним (повертає проміс при імпорті)
ЗастосуванняАсинхронна ініціалізація, динамічні імпорти
РизикиБлокування завантаження модулів, не можна в CommonJS

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.