Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Top-level await». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Top-level await це можливість писати `await` прямо на верхньому рівні модуля, поза будь-якою `async`-функцією.** Раніше для цього доводилося загортати код у функцію-обгортку на кшталт `init()` або `main()`. Тепер модуль, який містить такий `await`, сам стає асинхронним: його виконання призупиняється, доки проміс не виконається, а всі модулі, що його імпортують, чекають на нього по ланцюжку залежностей. Працює лише в ES-модулях (`.mjs`, `"type": "module"`, `<script type="module">`), у CommonJS з `require` цього немає. ```javascript // config.mjs const env = await fetch('/env.json').then(r => r.json()); export const API_URL = env.production ? 'https://api.example.com' : 'http://localhost:3000'; ``` **Ключове:** зручна асинхронна ініціалізація ціною того, що довгий `await` затримує старт усього дерева залежностей.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Top-level await це можливість використовувати `await` безпосередньо на верхньому рівні ES-модуля, поза `async`-функцією.** Модуль із таким `await` сам стає асинхронним: його завантаження призупиняється, доки проміс не виконається. ## Теорія ### TL;DR - Раніше `await` дозволявся тільки всередині `async`-функції, тому для ініціалізації писали обгортки `init()` чи `bootstrap()`. - Тепер `await` можна писати на верхньому рівні модуля, і модуль просто «чекає» на цьому рядку. - Працює лише в ES-модулях: `.mjs`, `.js` з `"type": "module"` у `package.json`, `<script type="module">`. У CommonJS недоступно. - Модуль, що використовує top-level await, при імпорті фактично повертає проміс, тому всі, хто його імпортує, теж чекають. - Основне застосування: асинхронна ініціалізація, читання конфігів, динамічні імпорти `await import(...)`. - Головний ризик: один довгий `await` блокує старт усього дерева залежностей. ### Швидкий приклад ```javascript // module.mjs або файл у проєкті з type: "module" const response = await fetch('https://api.example.com/user'); const user = await response.json(); console.log('Користувач:', user.name); ``` Код призупиняє завантаження модуля, доки проміс не виконається. Саме це й робить модуль асинхронним за своєю природою. ### Як було раніше До 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()` і подібні) і не забувати її викликати. ### Де можна використовувати Тільки в 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`. Якщо модуль `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 ``` ### Практичні приклади **Приклад 2, динамічна ініціалізація конфігу:** ```javascript // config.mjs const env = await fetch('/env.json').then(r => r.json()); export const API_URL = env.production ? 'https://api.example.com' : 'https://api.staging.example.com'; ``` Модуль `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); ``` Це дозволяє динамічно імпортувати модуль асинхронним синтаксисом, без зайвих обгорток. **Приклад у Node.js:** ```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-режимі. ### Переваги, ризики і підсумок Переваги: - Спрощує ініціалізацію модулів, немає потреби в `async init()` навколо коду. - Прибирає «піраміду імпортів» для асинхронних залежностей. - Дозволяє гнучко робити асинхронні імпорти через `await import()`. Ризики: - **Блокування ланцюжка модулів:** якщо один модуль «висить» на довгому `await`, усе дерево залежностей чекає на його завершення. - **Недоступність у CommonJS:** тільки ESM. - Може вплинути на час старту застосунку, особливо в Node.js. | Особливість | Опис | | --- | --- | | Що робить | Дозволяє використовувати `await` поза `async`-функціями | | Де працює | Тільки в ES-модулях | | Що повертає | Модуль стає асинхронним, при імпорті повертає проміс | | Застосування | Асинхронна ініціалізація, динамічні імпорти | | Ризики | Блокування завантаження модулів, недоступність у CommonJS | ### Типові помилки - **Намагатися використати top-level await у CommonJS.** У файлі без `"type": "module"` або без розширення `.mjs` це синтаксична помилка; спершу треба перевести проєкт чи файл на ESM. - **Забувати про `<script type="module">`.** У звичайному `<script>` top-level await не працює, бо це не модуль. - **Робити довгі мережеві запити на верхньому рівні часто імпортованого модуля.** Такий `await` затримує старт усіх, хто його імпортує, і застосунок довго «нічого не показує». Краще винести це в явну функцію ініціалізації або віддавати проміс. - **Вважати, що top-level await робить імпорти паралельними.** Модулі в ланцюжку чекають один на одного; для паралельних запитів усередині одного модуля потрібен `Promise.all`. - **Створювати циклічні залежності з top-level await.** Якщо два модулі чекають один на одного, виконання не завершиться взагалі. - **Не обробляти помилку.** Відхилений проміс на верхньому рівні провалює завантаження всього модуля, тому критичні місця варто загортати у `try...catch` і мати запасне значення.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.