Русский
Русский
English
Статистика
Реклама

Перевод Использование глобального await в JavaScript



Новая возможность, которая может изменить наш подход к написанию кода

JavaScript очень гибкий и мощный язык, который определяет развитие современного веба. Одна из основных причин главенства JavaScript в сфере веб-разработки заключается в его стермительном развитии и постоянном совершенствовании.

Одним из предложений по улучшению JavaScript является предложение под названием top-level await (await верхнего уровня, глобальный await). Цель данного предложения состоит в превращении ES модулей в некое подобие асинхронных функций. Это позволит модулям получать готовые к использованию ресурсы и блокировать модули, импортирующие их. Модули, которые импортируют ожидаемые ресурсы, смогут запускать выполнение кода только после получения ресурсов и их предварительной подготовки к использованию.

Указанное предложение в настоящее время находится на 3 стадии рассмотрения, поэтому данную возможность пока нельзя использовать в продакшне. Однако можно быть уверенным в том, что в ближайшем будущем она непременно будет реализована.

Не переживайте из-за этого. Продолжайте читать. Я покажу, как можно использовать названную фичу уже сейчас.

Что не так с обычным await?


Если вы попытаетесь использовать ключевое слово await за пределами асинхронной функции, то получите синтаксическую ошибку. Во избежание этого разработчики используют немедленно вызываемые функциональные выражения (Immediately Invoked Function Expression, IIFE).

await Promise.resolve(console.log("")); // Ошибка(async () => {    await Promise.resolve(console.log(""))})();

Указанная проблема и ее решение это лишь вершина айсберга


При работе с ES6 модулями вы, как правило, имеете дело с большим количеством экземпляров, экспортирующих и импортирующих значения. Рассмотрим пример:

// library.jsexport const sqrt = Math.sqrt;export const square = (x) => x * x;export const diagonal = (x, y) => sqrt((square(x) + square(y)));// middleware.jsimport { square, diagonal } from "./library.js";console.log("From Middleware");let squareOutput;let diagonalOutput;const delay = (ms) => new Promise((resolve) => {    const timer = setTimeout(() => {        resolve(console.log(""));        clearTimeout(timer);    }, ms);});// IIFE(async () => {    await delay(1000);    squareOutput = square(13);    diagonalOutput = diagonal(12, 5);})();export { squareOutput, diagonalOutput };

В приведенном примере мы экспортируем и импортируем переменные между library.js и middleware.js. Вы можете назвать файлы как угодно.

Функция delay возвращает промис, разрешающийся после задержки. Поскольку данная функция является асинхронной, мы используем ключевое слово await внутри IIFE для ожидания ее завершения. В реальном приложении вместо функции delay будет вызов fetch (запроса на получение данных) или другая асинхронная задача. После разрешения промиса, мы присваиваем значение нашей переменной. Это означает, что до разрешения промиса наша переменная будет иметь значение undefined.

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

Давайте взглянем на код, в котором эти переменные импортируются и используются:

// main.jsimport { squareOutput, diagonalOutput } from "./middleware.js";console.log(squareOutput); // undefinedconsole.log(diagonalOutput); // undefinedconsole.log("From Main");const timer1 = setTimeout(() => {    console.log(squareOutput);    clearTimeout(timer1);}, 2000); // 169const timer2 = setTimeout(() => {    console.log(diagonalOutput);    clearTimeout(timer2);}, 2000); // 13

Если вы запустите этот код, то в первых двух случаях получите undefined, а в третьем и четвертом 169 и 13, соответственно. Почему так происходит?

Это происходит из-за того, что мы пытаемся получить значения переменных, экспортируемых из middleware.js, в main.js до завершения выполнения асинхронной функции. Вы помните, что у нас там промис, ожидающий разрешения?

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

Обходные пути

Существует, как минимум, два способа решить обозначенную проблему.

1. Экспорт промиса для инициализации

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

// middleware.jsimport { square, diagonal } from "./library.js";console.log("From Middleware");let squareOutput;let diagonalOutput;const delay = (ms) => new Promise((resolve) => {    const timer = setTimeout(() => {        resolve(console.log(""));        clearTimeout(timer);    }, ms);});// обходной маневр или, как еще говорят, костыльexport default (async () => {    await delay(1000);    squareOutput = square(13);    diagonalOutput = diagonal(12, 5);})();export { squareOutput, diagonalOutput };

При получении доступа к экспортируемым переменным в main.js можно подождать выполнения IIFE.

// main.jsimport promise, { squareOutput, diagonalOutput } from "./middleware.js";promise.then(() => {    console.log(squareOutput); // 169    console.log(diagonalOutput); // 169    console.log("From Main");});const timer1 = setTimeout(() => {    console.log(squareOutput);    clearTimeout(timer1);}, 2000); // 169const timer2 = setTimeout(() => {    console.log(diagonalOutput);    clearTimeout(timer2);}, 2000); // 13

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

  • При использовании указанного шаблона приходится искать нужный промис
  • Если в другом модуле также используются переменные squareOutput и diagonalOutput, мы должны обеспечить реэкспорт IIFE

Существует и другой способ.

2. Разрешение промиса IIFE с экспортируемыми переменными

В данном случае, вместо экспорта переменных по-отдельности, мы возвращаем их из нашего асинхронного IIFE. Это позволяет файлу main.js просто ждать разрешения промиса и извлекать его значение.

// middleware.jsimport { square, diagonal } from "./library.js";console.log("From Middleware");let squareOutput;let diagonalOutput;const delay = (ms) => new Promise((resolve) => {    const timer = setTimeout(() => {        resolve(console.log(""));        clearTimeout(timer);    }, ms);});// обходной маневрexport default (async () => {    await delay(1000);    squareOutput = square(13);    diagonalOutput = diagonal(12, 5);    return { squareOutput, diagonalOutput };})();// main.jsimport promise from "./middleware.js";promise.then(({ squareOutput, diagonalOutput }) => {    console.log(squareOutput); // 169    console.log(diagonalOutput); // 169    console.log("From Main");});const timer1 = setTimeout(() => {    console.log(squareOutput);    clearTimeout(timer1);}, 2000); // 169const timer2 = setTimeout(() => {    console.log(diagonalOutput);    clearTimeout(timer2);}, 2000); // 13

Однако у такого решения также имеются некоторые недостатки.

Согласно предложению данный шаблон имеет серьезный недостаток, заключающийся в необходимости существенной реорганизации связанного источника ресурсов в более динамические шаблоны и размещении большей части тела модуля в функции обратного вызова .then() для обеспечения возможности использования динамических модулей. Это представляет собой значительный регресс с точки зрения возможности статического анализа, тестируемости, эргономики и многого другого по сравнению с модулями ES2015.

Как глобальный await решает данную проблему?


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

// middleware.jsimport { square, diagonal } from "./library.js";console.log("From Middleware");let squareOutput;let diagonalOutput;const delay = (ms) => new Promise((resolve) => {    const timer = setTimeout(() => {        resolve(console.log(""));        clearTimeout(timer);    }, ms);});// "глобальный" awaitawait delay(1000);squareOutput = square(13);diagonalOutput = diagonal(12, 5);export { squareOutput, diagonalOutput };// main.jsimport { squareOutput, diagonalOutput } from "./middleware.js";console.log(squareOutput); // 169console.log(diagonalOutput); // 13console.log("From Main");const timer1 = setTimeout(() => {    console.log(squareOutput);    clearTimeout(timer1);}, 2000); // 169const timer2 = setTimeout(() => {    console.log(diagonalOutput);    clearTimeout(timer2);}, 2000); // 13

Ни одна из инструкций в main.js не выполняется до разрешения промисов в middleware.js. Это гораздо более чистое решение по сравнению с обходными путями.

Заметка

Глобальный await работает только с ES модулями. Используемые зависимости должны быть указаны явно. Приведенный ниже пример из репозитория предложения хорошо это демонстирует.

// x.mjsconsole.log("X1");await new Promise(r => setTimeout(r, 1000));console.log("X2");// y.mjsconsole.log("Y");// z.mjsimport "./x.mjs";import "./y.mjs";// X1// Y// X2

Данный сниппет не выведет в консоль X1, X2, Y, как можно ожидать, поскольку x и y отдельные модули, не связанные между собой.

Я настоятельно рекомендую вам изучить рездел часто задаваемые вопросы предложения для лучшего пониманию рассматриваемой фичи.

Реализация


V8

Вы можете протестировать данную возможность уже сейчас.

Для этого перейдите в директорию, в которой размещается Chrome на вашей машине. Убедитесь в том, что все вкладки браузера закрыты. Откройте терминал и введите следующую команду:

chrome.exe --js-flags="--harmony-top-level-await"

Вы таже можете попробовать данную фичу в Node.js. Прочитайте это руководство, чтобы узнать больше.

ES модули

Убедитесь, что добавили тегу script атрибут type со значением module.

<script type="module" src="./index.js"></script>

Обратите внимание, что в отличие от обычных скриптов, ES6 модули следуют политике общего происхождения (одного источника) (SOP) и совместного использования ресурсов (CORS). Поэтому с ними лучше работать на сервере.

Случаи использования


Согласно предложению случаями использования глобального await является следующее:

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

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

Это позволяет модулям использовать значения среды выполнения для вычисления путей зависимостей и может быть полезным для разделения разработка/продакшн код, интернационализации, разделения кода в зависимости от среды выполнения (браузер, Node.js) и т.д.

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

const connection = await dbConnector()

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

Запасной вариант

В приведенном ниже примере показано, как глобальный await может использоваться для загрузки зависимости с реализацией запасного варианта. Если импорт из 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');}

Критика


Rich Harris составил список критических замечаний относительно await верхнего уровня. Он включает в себя следующее:

  • Глобальный await может блокировать выполнение кода
  • Глобальный await может блокировать получение ресурсов
  • Отсутствует поддержка CommonJS модулей

Вот какие ответы на эти замечания даются в FAQ предложения:

  • Поскольку дочерние узлы (модули) имеют возможность выполнения, блокировка кода, в конечном счете, отсутствует
  • Глобальный await используется на стадии выполнения графа модулей. На данном этапе все ресурсы получены и связаны, поэтому риска блокировки получения ресурсов не усматривается
  • await верхнего уровня ограничен ES6 модулями. Поддержка CommonJS модулей, как и обычных скриптов, изначально не планировалась

Я снова настоятельно рекомендую ознакомиться с FAQ предложения.

Надеюсь, мне удалось доступно объяснить суть рассматриваемого предложения. Собираетесь ли использовать эту возможность? Делитесь своим мнением в комментариях.
Источник: habr.com
К списку статей
Опубликовано: 19.10.2020 14:23:26
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Javascript

Программирование

Разработка веб-сайтов

Async/await

Top-level await

Await

Proposal

Feature

Асинхронная функция

Предложение

Возможность

Фича

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru