Условные асинхронные зависимости

Веб-функции: Top-level await

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

Используя await верхнего уровня, вы можете инкапсулировать логику условной загрузки в одном модуле, эффективно предотвращая выполнение последующих потребительских модулей до тех пор, пока зависимость не будет полностью загружена и готова.

Шаблон условного полифила

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

В следующем примере полифил атрибута popover загружается условно, если он отсутствует в HTMLElement.prototype:

// conditionally-load-polyfill.js

// Check if the feature is missing before doing work.
// MANDATORY: Prefer checking HTMLElement.prototype over window or document
// when checking for a global DOM attribute or property like popover.
if (!('popover' in HTMLElement.prototype)) {
  // Use top-level await to pause the execution of any module that imports this file 
  // until the polyfill finishes downloading and executing.
  await import('/path/to/popover-polyfill.js');
}

// Export a marker if needed by your application
export const polyfillLoaded = true;
// main.js

// MANDATORY: Because conditionally-load-polyfill.js uses top-level await, 
// this import will block execution of main.js until the polyfill is ready.
import './conditionally-load-polyfill.js';

// Now it is safe to use the feature (e.g., showing a popover)
const myPopover = document.getElementById('my-popover');
if (myPopover) {
  myPopover.showPopover();
}

Последовательный импорт модулей и устранение ошибки, возникающей в версиях Safari до 27

ОБЯЗАТЕЛЬНО. Структурируйте граф модулей так, чтобы модуль верхнего уровня await импортировался из одной точки входа и завершал работу до того, как будут оценены дочерние модули.

  • НЕ размещайте параллельные статические импорты в файле записи (например, import './conditionally-load-polyfill.js'; import './app.js';). Согласно спецификации оценки модулей ECMAScript, await верхнего уровня приостанавливает только родительский модуль, который его импортирует, а не параллельные статические импорты, поэтому ./app.js будет оцениваться одновременно во всех браузерах до завершения загрузки полифила.
  • НЕ импортируйте модуль верхнего уровня await из нескольких дочерних модулей одновременно (например, не используйте статичный импорт ./conditionally-load-polyfill.js в a.js и b.js). Хотя в Safari 27 загрузчик модулей WebKit был исправлен и теперь поддерживает одновременный импорт модулей верхнего уровня await, в Safari 15–26.6 при одновременном импорте модуля, содержащего await верхнего уровня, возникает ошибка (см. совместимость браузеров в MDN).

Вместо этого выберите один из двух шаблонов в зависимости от структуры модуля:

  1. Модуль с одной точкой входа (показан на иллюстрации main.js выше). Если ваш модуль входа запускает зависимую логику инициализации напрямую, статически импортируйте ./conditionally-load-polyfill.js в верхней части этого модуля и запустите код в его теле.
  2. Загрузка отдельных модулей приложения. Если ваш модуль входа делегирует полномочия модулям приложения нижнего уровня (например, ./app.js), свяжите динамические вызовы import() с await верхнего уровня, чтобы ./app.js извлекался и оценивался только после разрешения модуля полифила:
// main.js

// MANDATORY: Chain dynamic imports with top-level await when bootstrapping separate
// application modules so './app.js' waits for the polyfill to finish loading.
// DO NOT use sibling static imports (`import './conditionally-load-polyfill.js'; import './app.js';`),
// which evaluate concurrently and execute './app.js' before the polyfill is ready.
await import('./conditionally-load-polyfill.js');
await import('./app.js');

Резервные стратегии

Хотя базовый элемент верхнего уровня await поддерживается основными браузерами с 2021 года (Chrome 89, Edge 89, Firefox 89 и Safari 15), он стал полностью соответствовать спецификации только в Safari 27 (см. совместимость браузеров MDN).

  • Safari 15–26.6. Если вы используете шаблон с одной точкой входа, описанный в предыдущем разделе, и несколько дочерних модулей не импортируют одновременно модуль await верхнего уровня, вы можете использовать await верхнего уровня без дополнительного резервного варианта.
  • Устаревшие браузеры (выпущенные до 2021 г.). Если ваше приложение должно поддерживать браузеры, в которых не поддерживается await верхнего уровня, не используйте await верхнего уровня и поместите условную оркестровку import() в стандартную функцию async или цепочку промисов .then().