JavaScript top-level await

02 октября 2026
Автор

Антон Ларичев

Что такое top-level await

Top-level await — это возможность использовать ключевое слово await непосредственно на верхнем уровне ES-модуля, без оборачивания в асинхронную функцию. Функциональность стандартизирована в ECMAScript 2022 и поддерживается во всех современных браузерах и в Node.js начиная с версии 14.8.

До появления top-level await, чтобы использовать await вне функции, разработчики прибегали к паттерну немедленно вызываемой асинхронной функции (IIFE):

// До ES2022 — обходной путь
(async () => {
  const data = await fetch('/api/config').then(r => r.json());
  console.log(data);
})();

Теперь то же самое можно написать напрямую:

// ES2022 — top-level await
const data = await fetch('/api/config').then(r => r.json());
console.log(data);

Курс по теме

Основы JavaScript — курс

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

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

Только в ES-модулях

Top-level await работает исключительно внутри ES-модулей — файлы с расширением .mjs, файлы с "type": "module" в package.json или <script type="module"> в браузере. В CommonJS-модулях (require/module.exports) он недоступен.

// ES-модуль (module.mjs или при "type": "module" в package.json)
const response = await fetch('https://api.example.com/data');
const json = await response.json();

export { json };

Попытка использовать top-level await в CommonJS вызовет синтаксическую ошибку:

// Ошибка в CommonJS
const data = await Promise.resolve(42); // SyntaxError: await is only valid in async functions

Влияние на загрузку модулей

Ключевое свойство top-level await — он блокирует выполнение импортирующего модуля до завершения асинхронной операции. Граф зависимостей при этом работает следующим образом:

  • Модуль с top-level await приостанавливает выполнение до разрешения промиса
  • Все модули, которые импортируют этот модуль, тоже ждут его завершения
  • Остальные независимые ветки графа зависимостей выполняются параллельно
// moduleA.mjs
console.log('A: старт');
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('A: завершён');
export const valueA = 'A';

// moduleB.mjs
console.log('B: старт');
export const valueB = 'B';

// main.mjs
import { valueA } from './moduleA.mjs';
import { valueB } from './moduleB.mjs';

// Порядок вывода в консоль:
// A: старт
// B: старт
// (через 1 секунду)
// A: завершён
// (затем выполняется main.mjs)
console.log(valueA, valueB); // 'A' 'B'

Модули A и B начинают загружаться параллельно, но main.mjs дожидается обоих прежде чем продолжить выполнение.

Практические применения

Инициализация конфигурации

Один из самых распространённых случаев — загрузка конфигурации перед экспортом значений из модуля:

// config.mjs
let config;

try {
  const response = await fetch('/api/config');
  config = await response.json();
} catch {
  config = { theme: 'light', lang: 'ru' }; // значения по умолчанию
}

export { config };
// app.mjs
import { config } from './config.mjs';

// config здесь гарантированно уже загружен
console.log(config.theme);

Условный импорт модулей

Top-level await позволяет динамически выбирать реализацию в зависимости от условия:

// i18n.mjs
const userLang = navigator.language.startsWith('ru') ? 'ru' : 'en';

const { messages } = await import(`./locales/${userLang}.mjs`);

export { messages };
// polyfills.mjs
if (!globalThis.fetch) {
  await import('whatwg-fetch');
}

export {};

Инициализация подключения к базе данных

В Node.js top-level await удобен для инициализации соединений перед стартом сервера:

// db.mjs
import { createConnection } from './db-driver.mjs';

const connection = await createConnection({
  host: process.env.DB_HOST,
  port: 5432,
  database: 'myapp',
});

await connection.ping();

export { connection };
// server.mjs
import { connection } from './db.mjs';
import express from 'express';

// К этому моменту соединение с БД уже установлено
const app = express();

app.get('/users', async (req, res) => {
  const users = await connection.query('SELECT * FROM users');
  res.json(users);
});

app.listen(3000);

Загрузка тяжёлых зависимостей по условию

// heavy-processor.mjs
const { processImage } = await (
  typeof ImageData !== 'undefined'
    ? import('./image-worker.mjs')
    : import('./image-fallback.mjs')
);

export { processImage };

Обработка ошибок

Если промис на верхнем уровне отклоняется, это приводит к ошибке загрузки всего модуля. Импортирующий модуль получит ошибку при попытке использовать такой модуль. Поэтому важно оборачивать рискованные операции в try/catch:

// Непойманная ошибка ломает весь модуль
const data = await fetch('/api/might-fail').then(r => r.json());
// Если запрос упадёт — модуль не загрузится вообще
// Правильно — обрабатываем ошибку явно
let data = null;
try {
  data = await fetch('/api/might-fail').then(r => r.json());
} catch (error) {
  console.error('Не удалось загрузить данные:', error);
}

export { data };

Ошибку загрузки модуля с top-level await можно перехватить через динамический import():

// main.mjs
try {
  const module = await import('./risky-module.mjs');
  console.log(module.data);
} catch (error) {
  console.error('Модуль не загрузился:', error);
}

Подводные камни

Циклические зависимости

Top-level await в сочетании с циклическими зависимостями может привести к взаимоблокировке. Если модуль A ждёт завершения модуля B, а модуль B ждёт завершения модуля A — оба зависнут навсегда. Избегайте top-level await в модулях, которые участвуют в циклических зависимостях.

Последовательность вместо параллельности

Внутри ES-модуля await работает последовательно так же, как и в обычной async-функции:

// Медленно: запросы выполняются последовательно
const users = await fetchUsers();
const posts = await fetchPosts();

// Быстро: запросы выполняются параллельно
const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);

export { users, posts };

Производительность загрузки страницы

Чрезмерное использование top-level await в браузерных модулях может замедлить начальную загрузку страницы — каждый зависимый модуль вынужден ждать завершения асинхронных операций. Выносите медленные инициализации в модули, которые загружаются лениво (по требованию).

// Плохо: тяжёлый модуль блокирует всё дерево зависимостей
import { heavyChart } from './heavy-chart-lib.mjs'; // heavy-chart-lib использует top-level await

// Лучше: загружаем по требованию
const loadChart = async () => {
  const { heavyChart } = await import('./heavy-chart-lib.mjs');
  return heavyChart;
};

Настройка Node.js

Чтобы использовать top-level await в Node.js, необходимо убедиться, что файл является ES-модулем.

Через package.json:

{
  "type": "module"
}

Или через расширение файла:

# Файл должен называться script.mjs
node script.mjs

Или через флаг при передаче кода через stdin:

node --input-type=module <<EOF
const data = await Promise.resolve(42);
console.log(data);
EOF

Поддержка окружений

Окружение Версия с поддержкой
Chrome 89+
Firefox 89+
Safari 15+
Node.js 14.8+ (только ESM)
Deno Полная
Bun Полная

Сравнение с паттернами до ES2022

// Паттерн 1: IIFE
let config;
(async () => {
  config = await loadConfig();
  startApp(config); // логика запуска вынуждена быть внутри IIFE
})();
// Паттерн 2: цепочка .then()
loadConfig()
  .then(config => startApp(config))
  .catch(console.error);
// Паттерн 3: top-level await — линейный и читаемый
const config = await loadConfig();
startApp(config);

Top-level await устраняет необходимость в обходных путях и делает асинхронный код на верхнем уровне модуля таким же читаемым, как синхронный, при этом не меняя семантику работы с промисами.

Чтобы глубже разобраться с асинхронным JavaScript и освоить современные возможности языка, пройдите курс на PurpleSchool: JavaScript с нуля до профи.

Стрелочка влевоJavaScript Web Crypto API — криптография в браузереМетод then() в JavaScriptСтрелочка вправо

Постройте личный план изучения Javascript до уровня Middle — бесплатно!

Javascript — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Javascript

Как работает метод trim() - JavaScriptКак работает метод toUpperCase() - JavaScriptКак работает метод toLowerCase() - JavaScriptКак работает метод substring() - JavaScriptКак работает метод startsWith() - JavaScriptКак работает метод split() - JavaScriptКак работает метод slice() - JavaScriptКак работает метод search() - JavaScriptКак работает метод replaceAll() - JavaScriptКак работает метод replace() - JavaScriptКак работает метод repeat() - JavaScriptКак работает метод padStart() - JavaScriptКак работает метод padEnd() - JavaScriptКак работает метод match() - JavaScriptКак работает метод localeCompare() - JavaScriptКак работает метод matchAll() - JavaScriptКак работает метод indexOf() - JavaScriptКак работает метод lastIndexOf() - JavaScriptКак работает свойство length - JavaScriptКак работает метод includes() - JavaScriptКак работает метод fromCodePoint() - JavaScriptКак работает метод fromCharCode() - JavaScriptКак работает метод endsWith() - JavaScriptКак работает метод concat() - JavaScriptКак работает метод codePointAt() - JavaScriptКак работает метод charCodeAt() - JavaScriptКак работает метод charAt() - JavaScript
Итератор в JavaScript
try...catch в JavaScriptError в JavaScript
Событие wheel в JavaScriptСобытие unload в JavaScriptСобытие touch в JavaScriptСобытие submit в JavaScriptСобытие reset в JavaScriptСобытие scroll в JavaScriptМетод .preventDefault() в JavaScriptСобытие mouseover в JavaScriptСобытие mouseout в JavaScriptСобытие load в JavaScriptСобытие keyup в JavaScriptСобытие keydown в JavaScriptСобытие invalid в JavaScriptСобытие input в JavaScriptСобытийная модель Event в JavaScriptОбъект события Event в JavaScriptСобытие DOMContentLoaded в JavaScriptСобытие dblclick в JavaScriptСобытие click в JavaScriptСобытие change в JavaScriptBroadcast Channel API в JavaScriptJavaScript BroadcastChannel — межвкладочное взаимодействиеСобытие beforeunload в JavaScript
Error cause — цепочки ошибок в JavaScript
Методы массивов toSorted, toReversed и withКак работает метод some() - JavaScriptКак работает метод reverse() - JavaScriptКак работает метод reduce() - JavaScriptКак работает метод map() - JavaScriptКак работает метод isArray() - JavaScriptКак работает метод indexOf() - JavaScriptКак работает метод includes() - JavaScriptКак работает метод from() - JavaScriptКак работает метод forEach() - JavaScriptКак работает метод flatMap() - JavaScriptКак работает метод flat() - JavaScriptКак работает метод findIndex() - JavaScriptКак работает метод find() - JavaScriptКак работает метод filter() - JavaScriptКак работает метод every() - JavaScriptМассивы в JavaScriptArray.at, findLast, findLastIndex — новые методы массивов
Открыть базу знаний

Лучшие курсы по теме

изображение курса

Основы JavaScript

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий