await верхнего уровня

await верхнего уровня

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

Старое поведение

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

await Promise.resolve(console.log('ice cream'));
// → SyntaxError: await is only valid in async function (async function() { await Promise.resolve(console.log('ice cream')); // → ice cream
}());

Новое поведение

С await верхнего уровня приведенный выше код работает так, как вы ожидаете от модулей:

await Promise.resolve(console.log('ice cream'));
// → ice cream

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

Сценарии использования

Эти варианты использования взяты из репозитория спецификации.

Динамические пути зависимостей

const strings = await import(`/i18n/${navigator.language}`);

Это позволяет модулям использовать значения во время выполнения для определения зависимостей. Это полезно для таких вещей, как разделение разработки / производства, интернационализация, разделение среды и т. д.

Инициализация ресурса

const connection = await dbConnector();

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

Резервные зависимости

В следующем примере делается попытка загрузить библиотеку JavaScript из CDN A, а в случае сбоя обратиться к CDN B:

let jQuery;
try { jQuery = await import('https://cdn-a.example.com/jQuery');
} catch { jQuery = await import('https://cdn-b.example.com/jQuery');
}

Порядок исполнения модуля

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

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

Вот что происходит, когда вы используете в модуле await верхнего уровня:

Выполнение текущего модуля откладывается до разрешения ожидаемого промиса.

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

Смежные модули и смежные модули родительских модулей могут продолжать выполнение в том же синхронном порядке — при условии, что в графе нет циклов или других ожидаемых промисов.

Модуль, который вызвал await, возобновляет свое выполнение после разрешения ожидаемого промиса.

Родительский модуль и последующие деревья продолжают выполняться в синхронном порядке до тех пор, пока нет других ожидаемых промисов.

Разве это уже не работает в DevTools?

Это действительно так! REPL в Chrome DevTools, Node.js и Safari Web Inspector уже некоторое время поддерживают await верхнего уровеня. Однако этот функционал был нестандартным и ограничивался REPL! Он отличается от предложения await верхнего уровня, которое является частью спецификации языка и применяется только к модулям. Чтобы протестировать производственный код, опирающийся на await верхнего уровня, таким образом, чтобы он полностью соответствовал семантике предложения спецификации, обязательно тестируйте его в реальном приложении, а не только в DevTools или Node.js REPL!

Разве await верхнего уровня не является выстрелом себе в ногу?

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

await верхнего уровня может заблокировать выполнение.

await верхнего уровня может блокировать извлечение ресурсов.

Не будет четкой истории взаимодействия для модулей CommonJS.

Версия предложения 3-го этапа напрямую решает следующие проблемы:

Поскольку смежные модули могут выполняться, никакой полной блокировки нет.

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

await верхнего уровня ограничен модулями. Явно не поддерживается ни скрипты, ни модули CommonJS.

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

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

Автор: Myles Borins

Источник: https://v8.dev

Редакция: Команда webformyself.