Home » Внутреннее устройство async/await в JavaScript

Внутреннее устройство async/await в JavaScript

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

Чего мы пытаемся достичь?

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

2

return new Promise((resolve, reject) => {

4

resolve('Timeout resolved');

12

const result = await wait();

19

main().then((result) => {

Вывод приведенного выше кода:

Учитывая приведенный выше фрагмент кода и соответствующий вывод, можем ли мы переписать main() функционировать, чтобы не использовать async и await ключевые слова, но при этом добиться того же результата? Условия следующие:

  • Невозможно использовать цепочку обещаний внутри main(). Это делает задачу тривиальной и отклоняет от первоначальной цели. Цепочка обещаний может работать в приведенном выше надуманном примере, но она не отражает всей сути async/await и проблем, которые он решает.
  • Не менять сигнатуры функций. Изменение сигнатур функций также потребует обновления вызовов функций, что сложно в большом проекте со множеством взаимозависимых функций. Поэтому постарайтесь не менять сигнатуры функций.

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

Детская площадка

Этот пост в блоге является практическим, и я настоятельно рекомендую вам использовать приведенную ниже площадку, чтобы проверить свои идеи по реализации чего-то вроде async/await без использования async и await ключевые слова. Остальная часть статьи содержит подсказки и решения о том, как этого можно добиться, но не стесняйтесь воспользоваться моментом и опробовать какой-нибудь код, когда у вас появится идея продолжить.

Подсказка № 1: рассмотрим приостановку и возобновление выполнения функции.

На высоком уровне, что делает async/await? Ну, это паузы выполнение асинхронной функции всякий раз, когда она встречает await заявление. И резюме выполнение, когда ожидаемое обещание разрешено (или выдает ошибку). Здесь возникает естественный вопрос: как приостановить и возобновить выполнение функции. Функции имеют тенденцию выполняться до завершения, верно?

Есть ли в JavaScript встроенная функция, имитирующая такое поведение? Да! Его Генераторы.

Read more:  Выпущена бета-версия JQuery 4.0.0 с важными обновлениями и критическими изменениями

Генераторы — это особый тип функции, которая может возвращать несколько фрагментов данных в течение его исполнение. Традиционные функции могут возвращать несколько данных, используя такие структуры, как массивы и объекты, но генераторы возвращают данные всякий раз, когда вызывающая сторона запрашивает их, и они Пауза исполнение до тех пор, пока они не будут попросили продолжить генерировать и вернуть больше данных.

Я не буду углубляться в то, на что способны генераторы, пожалуйста, используйте связанную ссылку. Вот фрагмент кода, объясняющий концепцию генераторов. Попробуйте следить за комментариями, чтобы понять, что происходит.

4

const message = yield 'Result 1';

19

console.log(it.next());

29

console.log(it.next('Message Passing'));

40

console.log(it.next());

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

Подсказка №2: Когда возобновить выполнение функции?

Генераторы предоставляют возможность приостанавливать и возобновлять выполнение функции. Вспоминая, как работает async/await, асинхронная функция приостанавливается при обнаружении await заявление. Таким образом, мы можем рассматривать асинхронную функцию как функцию генератора и поместить yield оператор рядом с обещанием, чтобы он остановился на этом этапе. Выглядит примерно так:

Но когда должен сработать генератор резюме его исполнение? Оно должно возобновиться, когда обещание вблизи выхода решено. Как вызывающая сторона может узнать об обещании, если оно находится внутри функции генератора? Есть ли способ предоставить обещание вызывающему абоненту, чтобы он мог прикрепить .then() обратный вызов к нему, который вызовет .next() на объекте-генераторе, чтобы возобновить выполнение?

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

4

const result = yield wait();

12

it.next().value.then(() => {

Подсказка № 3: Сделайте разрешенные данные промиса доступными для генератора

В предыдущем фрагменте кода мы смогли успешно приостановить и возобновить выполнение функции, когда обещание было выполнено. Но функция-генератор не получает разрешенные данные из обещания. result переменная в main() функция должна иметь "Timeout resolved" как мы видим при использовании async/await. Но в нашей реализации он не получает данные, которые дает промис при его разрешении. Есть ли способ передать решенные данные обещания генератору? В конце концов, вызывающая сторона имеет доступ к разрешенным данным, поскольку генератор выдает обещание. Так может ли он передать эти данные обратно в функцию генератора, когда вызывающая сторона вызывает .next() на объекте-генераторе? Мы уже сталкивались с этим поведением передачи сообщений выше в этом посте 😉

Read more:  FDA одобрило устройство для колоноскопии с искусственным интеллектом

.next() функция принимает аргумент, который доступен до последнего yield оператор, в котором генератор был приостановлен. Поэтому, чтобы передать разрешенные данные обещания, мы просто вызываем .next() с разрешенными данными из обещания.

4

const result = yield wait();

12

it.next().value.then((resolvedData) => {

13

it.next(resolvedData);

Благодаря этому изменению у нас есть базовая реализация async/await без использования async и await ключевые слова. Обратите внимание на main() функцию и сравните ее с ее асинхронным аналогом. Они поразительно похожи, правда? Вместо использования async functionэто function *и вместо awaitоно использует yield ключевое слово. В этом красота этой реализации!

Мы зашли довольно далеко! Если вы смогли разобраться с некоторыми из этих шагов самостоятельно, похлопайте себя по плечу 💚

Подсказка № 4: Расширьте его для работы с несколькими yield заявления

Следующий шаг — заставить нашу реализацию работать с произвольным количеством yield заявления. Приведенный выше фрагмент работает только с одним yield как он называет .next() только после того, как первое обещание будет выполнено. Но генератор может выдавать произвольное количество обещаний. Можем ли мы написать абстракцию, которая динамически ожидает разрешения любого полученного обещания, а затем вызывает .next()?

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

Вот как выглядит код, ваша реализация может отличаться.

1

run(main).then((result) => {

5

function run(fn, ...args) {

6

const it = fn(...args);

8

return new Promise((resolve, reject) => {

Подсказка №5: Звонок .next() произвольное количество раз

Теперь давайте сосредоточимся на реализации run() функция. Он должен позвонить .next() на объекте-генераторе до тех пор, пока выполняются обещания. Можем ли мы использовать циклы для этого? Будет ли это работать так, как ожидалось, когда мы используем обещания? Конечно, нет, мы не можем использовать циклы, поскольку они будут продолжать вызывать .next() на объекте-генераторе, не дожидаясь выполнения обещаний. Есть ли лучший способ зацикливания, в котором нет этой проблемы?

Это рекурсия! Используя рекурсию, мы можем продолжать вызывать .next() на объекте-генераторе, когда полученные обещания разрешаются. Каково условие выхода или базовый вариант завершения рекурсии? Мы хотим остановиться, когда закончится работа генератора. Что значит .next() вернуться, когда генератор достиг конца? done для свойства возвращаемого объекта установлено значение true!

1

function run(fn, ...args) {

2

const it = fn(...args);

4

return new Promise((resolve, reject) => {

6

const result = it.next();

13

result.value.then((resolvedValue) => {

Read more:  Кардиологическое устройство, изготовленное компанией, приобретенной J&J, оказалось полезным в независимом исследовании

Мы не проходим resolvedValue от обещания обратно к генератору. Для этого сделаем step() функция принимает аргумент. Также обратите внимание, как обещание, возвращаемое run никогда не решается. Потому что мы не вызываем resolve() функционируйте где угодно! Когда обещание должно быть выполнено? Когда генератор заканчивается и больше нечего выполнять. Чем должно разрешиться обещание? С тем, что возвращает функция-генератор, поскольку это соответствует поведению асинхронных функций.

1

function run(fn, ...args) {

2

const it = fn(...args);

4

return new Promise((resolve, reject) => {

5

function step(resolvedValue) {

6

const result = it.next(resolvedValue);

10

resolve(result.value);

14

result.value.then((resolvedValue) => {

Вот и все – асинхронность/ожидание без async и await!

На этом реализация async/await завершена без использования async и await ключевые слова. Асинхронные функции представлены как функции-генераторы и вместо использования await мы используем yield заявления, чтобы дождаться решения обещаний. Для разработчика, использующего нашу реализацию, она по-прежнему похожа на async/await и не нарушает двух условий, установленных в начале этой статьи.

Это именно то, что делают транспиляторы, такие как Babel, при преобразовании async/await в более старые версии JavaScript, которые изначально не имели этой функции. Если вы посмотреть транспилированный кодвы можете провести много параллелей с нашей реализацией выше!



async/await переданного кода с помощью Babel

Следующие шаги

Приведенная выше реализация охватывает только успешный путь async/await. Наша реализация не обрабатывает сценарии ошибок, когда обещание отклонено. Я хотел бы оставить это в качестве упражнения для читателей, поскольку это очень похоже на путь успеха, только используемые функции другие. Обязательно взгляните на API генераторов чтобы увидеть, есть ли способ передать ошибки обратно генератору, подобному .next() функция для начала! Если вы решите эту проблему, поделитесь ею со мной в Твиттере и не забудьте отметить меня — @blenderskool.

Заключение

Когда я впервые столкнулся с этой реализацией, я был поражен ее красотой и простотой. Я знал, что async/await — это синтаксический сахар, но не знал, как он выглядит «под капотом». В этом посте рассматривается именно этот аспект async/await и то, как он связан с промисами и генераторами. Мне нравится время от времени разгадывать абстракции, чтобы понять, как все работает под капотом. И именно здесь я нахожу интересные концепции для изучения. Я надеюсь, что этот пост также побудил вас сохранять любопытство и изучать вещи на практике, а не просто следовать учебнику 🙌.


2023-09-21 17:31:00


1695645315
#Внутреннее #устройство #asyncawait #JavaScript

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.