Как избавиться от вложенных коллбэков: рассматриваем на примере приготовления гамбургера
Адаптированный перевод статьи Zell Liew «How to deal with nested callbacks and avoid callback hell».
В JavaScript есть странные вещи. Одна из них — обратный вызов, который находится в обратном вызове, который находится в обратном вызове. На английском языке это называется callback hell или ад обратных вызовов. Статья поможет справиться с этой проблемой и писать код понятнее.
Что такое обратные вызовы: функции, которые выполняются после выполнения других функций
Обратный вызов или коллбэк (англ. callback) — функция, которая выполняется после выполнения другой функции. Если вы не понимаете, о чем речь, прочитайте статью об обратных вызовах, а потом возвращайтесь к этому материалу.
В чём проблема вложенных коллбэков: код становится непонятным
Вложенные обратные вызовы выглядят так:
За такие конструкции мы любим недолюбливаем JavaScript. Вложенные коллбэки буквально сбивают с толку и взрывают мозг. Но эта проблема решается.
Как избегать вложенных вызовов: четыре рецепта
Сначала просто посмотрим на варианты решения проблемы без углубления в детали. Вот четыре способа вырваться из ада вложенных коллбэков:
Но прежде чем разбираться с каждым способом, давайте создадим свой callback hell. Это нужно, чтобы почувствовать боль вложенных обратных вызовов.
Делаем больно: откуда берутся вложенные коллбэки
Представьте, что готовите гамбургер. Вот алгоритм приготовления:
Давайте опишем приготовление гамбургера с помощью JavaScript:
Теперь представьте, что учите ребенка делать гамбургер. Вы инструктируете маленького помощника, то есть объясняете ему каждый шаг алгоритма. После каждой инструкции вы ждете, пока маленький повар выполнит её. Только после этого вы переходите к следующему шагу.
В JavaScript ожидание выражается с помощью коллбэков. Чтобы приготовить гамбургер, сначала нам надо отварить говядину. Но мы сможем положить мясо в кастрюлю только после того, как купим его в магазине.
Когда это сделано, можем положить мясо и остальную начинку между булочками.
Ну что, еще хотите гамбургер, или вложенные коллбэки испортили аппетит и настроение? Давайте посмотрим на способы решения проблемы, может, это вас тонизирует.
Первое решение: комментируйте код
Функцию makeBurger из примера выше легко понять, так как это достаточно простой случай. Эта функция просто не очень элегантная. Но новичка она может заставить задуматься. Если вы недавно изучаете программирование и сразу не понимаете, что происходит в коде, постарайтесь объяснить происходящее с помощью комментариев.
Комментарии объясняют вам или другому разработчику, что происходит в коде. Кто-то благодаря этим пояснениям поймет функцию и не бросит программирование.
Второе решение: разделяйте большие функции на несколько маленьких
Чтобы приготовить говядину, нужно положить её в духовку, установить температуру 200 °C и включить таймер на 20 минут. Вот код:
Запомните этот принцип: функции с вложенными коллбэками надо разделять на несколько маленьких функций.
Третье решение: используйте промисы
Если вы не изучали промисы, это можно сделать в курсе «Асинхронное программирование».
Промисы решают проблему вложенных обратных вызовов. Посмотрите на код:
Алгоритм приготовления гамбургера можно выразить ещё проще, если использовать промисы с одним аргументом:
Код стал понятнее. Давайте посмотрим, как превратить функцию с вложенными коллбэками в промисы.
Как заменить функцию с коллбэками на промисы
Если вы используете Node, все функции с обратными вызовами имеют одинаковый синтаксис:
Одинаковый синтаксис коллбэков позволяет использовать библиотеки типа ES6 Promisify или Denodeify. Если вы пользуетесь версией Node 8.0 и выше, можно применять библиотеку util.promisify. Эти инструменты конвертируют коллбэки в промисы.
Четвертое решение: используйте асинхронные функции
Чтобы использовать четвертое решение, нужно понимать следующие вещи:
Да, то же самое можно записать с помощью промисов. Но синтаксис async/await более понятный.
Завершаем: рассмотрели четыре способа решения проблемы вложенных коллбэков
Оригинал публикации: How to deal with nested callbacks and avoid callback hell. Мнение автора оригинальной статьи может не совпадать с позицией редакции.
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Что такое «ад обратного вызова» и как и почему RX решает его?
Может ли кто-то дать четкое определение вместе с простым примером, который объясняет, что такое «обратный ад» для тех, кто не знает JavaScript и node.js?
Когда (в каких настройках) возникает «проблема аддонов обратного вызова»?
Почему это происходит?
Является ли «callback hell» всегда связанным с асинхронными вычислениями?
Или может ли «callback hell» встречаться также в однопоточном приложении?
Я взял реактивный курс в Coursera и Эрик Мейер, сказав в одном из своих лекций, что RX решает проблему «обратного ада». Я спросил, что такое «callback hell» на форуме Coursera, но я не получил четкого ответа.
После объяснения «callback hell» на простом примере вы могли бы также показать, как RX решает проблему «callback hell» на этом простом примере?
ОТВЕТЫ
Ответ 1
1) Что такое «ад-колбэк» для тех, кто не знает javascript и node.js?
У этого другого вопроса есть несколько примеров адского обратного вызова Javascript: как избежать длительного вложения асинхронных функций в Node.js
Например, я хочу запустить код, который выглядит следующим образом:
Что произойдет, если теперь я захочу сделать функции getData асинхронными, то есть у меня будет возможность запустить какой-то другой код, пока я жду, пока они вернут свои значения? В Javascript единственным способом было бы переписать все, что касается асинхронных вычислений, используя стиль передачи продолжения:
Я не думаю, что мне нужно кого-то убеждать, что эта версия хуже, чем предыдущая. 🙂
2) Когда (в каких настройках) возникает «проблема ада обратного вызова»?
Когда у вас есть много функций обратного вызова в вашем коде! С ними труднее работать, чем больше их в вашем коде, и особенно плохо, когда вам нужно делать циклы, блоки try-catch и тому подобное.
Вместо этого нам может понадобиться написать:
Количество вопросов, которые мы получаем здесь на StackOverflow, спрашивающих, как делать такие вещи, является свидетельством того, насколько это запутано 🙂
3) Почему это происходит?
4) Или «ад-коллбэк» может происходить и в однопоточном приложении?
Вы все еще можете иметь параллельный код в однопоточном контексте. На самом деле JavaScript, королева ада обратного вызова, является однопоточным.
5) Не могли бы вы также показать, как RX решает «проблему ада обратного вызова» на этом простом примере.
В частности, я ничего не знаю о RX, но обычно эта проблема решается путем добавления встроенной поддержки асинхронных вычислений на языке программирования. Реализации могут различаться и включать в себя: асинхронность, генераторы, сопрограммы и callcc.
В Python мы можем реализовать этот предыдущий пример цикла с чем-то вроде:
Ответ 2
Просто ответьте на вопрос: не могли бы вы также показать, как RX решает проблему «callback hell» на этом простом примере?
Ответ 3
Чтобы решить вопрос о том, как Rx решает ад-коллбэк:
Сначала давайте снова опишем адский колбэк.
Каждый обратный вызов является вложенным. Каждый внутренний обратный вызов зависит от его родителя. Это приводит к «пирамиде гибели» в стиле обратного вызова ада. Код выглядит как знак>.
Чтобы решить это в RxJs, вы можете сделать что-то вроде этого:
С mergeMap оператора mergeMap AKA flatMap вы можете сделать его более кратким:
Как видите, код выровнен и содержит одну цепочку вызовов методов. У нас нет «пирамиды гибели».
Следовательно, обратного вызова ада избегают.
Ответ 4
Это часто происходит, когда поведение имеет зависимости, т.е. когда A должно произойти до того, как B должен произойти до C. Затем вы получите код следующим образом:
Если у вас много зависимостей поведения в вашем коде, как это, он может быстро получить проблемы. Особенно, если он веткится.
Это не будет сделано. Как мы можем выполнить асинхронный код в определенном порядке без необходимости передавать все эти обратные вызовы?
Что такое «callback hell» и как и почему RX решает его?
когда (в каких настройках) возникает «проблема обратного вызова»?
Почему это происходит?
всегда ли» callback hell » связан с асинхронными вычислениями?
или может «обратный вызов ада» произойти также в однопоточном приложении?
Я взял Реактивный курс на Coursera и Эрик Мейер сказал в одной из своих лекций, что RX решает проблему «обратного вызова ад». Я спросил, Что такое «обратный вызов ада» на форуме Coursera, но я не получил четкого ответа.
после объяснения » обратного вызова ада «на простом примере, не могли бы вы также показать, как RX решает» проблему обратного вызова ада » на этом простом примере?
5 ответов
в этом другом вопросе есть несколько примеров обратного вызова Javascript hell:как избежать длительного вложения асинхронных функций в узел.js
например, скажем, я хочу запустить код, который выглядит так:
что произойдет, если теперь я хочу сделать функции getData асинхронными, что означает, что у меня есть шанс запустить какой-то другой код, пока я жду, когда они вернут свои значения? В Javascript единственным способом было бы переписать все, что касается асинхронного вычисления, используя продолжение прохождения стиле:
Я не думаю, что мне нужно убеждать кого-либо, что эта версия уродливее предыдущей. 🙂
2) Когда (в каких настройках) возникает «проблема обратного вызова»?
когда у вас есть много функций обратного вызова в коде! Становится все труднее работать с ними, чем больше их у вас в коде, и это становится особенно плохо, когда вам нужно делать петли, пытаться поймать блоки и тому подобное.
например, насколько я знаю, в JavaScript единственный способ выполнить ряд асинхронных функций, где один запускается после предыдущего returns использует рекурсивную функцию. Вы не можете использовать цикл for.
вместо этого нам, возможно, придется написать:
количество вопросов, которые мы получаем здесь на StackOverflow, спрашивая, как это сделать, свидетельствует о том, насколько это запутанно 🙂
4) или может «обратный вызов ада» произойти также в однопоточном приложении?
асинхронное программирование связано с параллелизмом, а однопоточное-с параллелизмом. Эти два понятия на самом деле не одно и то же.
вы все еще можете иметь параллельный код в однопоточном контексте. Фактически, JavaScript, королева ада обратного вызова, однопоточен.
5) не могли бы вы также показать, как RX решает «проблему ада обратного вызова» на этом простом примере.
это не полный код, но идея в том, что» yield » приостанавливает наш цикл for, пока кто-то не позвонит myGen.следующий.)( Важно то, что мы все еще можем написать код, используя цикл for, без необходимости выворачивать логику «наизнанку», как мы должны были сделать в этом рекурсивном
просто ответьте на вопрос: не могли бы вы также показать, как RX решает «проблему ада обратного вызова» на этом простом примере?
Callback hell-это любой код, в котором использование обратных вызовов функций в асинхронном коде становится неясным или трудным для отслеживания. Как правило, когда существует более одного уровня косвенности, код с помощью обратных вызовов может стать сложнее следовать, сложнее рефакторировать и сложнее тестировать. Запах кода-это несколько уровней отступов из-за прохождения нескольких слоев функциональных литералов.
это часто происходит, когда поведение имеет зависимости, т. е. когда A должно произойти до B должно произойти до C. Затем вы получаете такой код:
Если у вас есть много поведенческих зависимостей кода, это может быть хлопотно быстро. Особенно если она разветвляется.
Так не пойдет. Как мы можем заставить асинхронный код выполняться в определенном порядке без необходимости передавать все эти обратные вызовы?
RX-сокращение от «реактивных расширений». Я не использовал его, но Googling предполагает, что это структура на основе событий, что имеет смысл. события являются общим шаблоном для выполнения кода в порядке без создания хрупкой связи. Вы можете заставить C слушать событие «bFinished», которое происходит только после того, как B называется прослушиванием «aFinished». Затем вы можете легко добавить дополнительные шаги или расширить такое поведение и можете легко проверить что ваш код выполняется в порядке, просто транслируя события в вашем тестовом примере.
для решения вопроса о том, как Rx решает обратного вызова ад:
сначала давайте снова опишем callback hell.
каждый обратный вызов вложен. Каждый внутренний обратный вызов зависит от своего родителя. Это приводит к стилюобратного вызова ад. Код выглядит как знак+.
чтобы решить эту проблему в RxJs, вы можете сделать что-то вроде этого:
С mergeMap АКА flatMap оператор вы могли бы сделать его более кратким:
как вы можете видеть, код сглажен и содержит одну цепочку вызовов методов. У нас нет»пирамиды судьбы».
следовательно, обратный вызов ад избежавший.
если вам интересно,обещания еще один способ избежать обратного вызова ада, но обещания готовностью, а не лень как наблюдаемых, так и (вообще говоря) вы не можете отменить их так же легко.
JavaScript-движки: как они работают? От стека вызовов до промисов — (почти) всё, что вам нужно знать
Вы когда-нибудь задумывались, как браузеры читают и исполняют JavaScript-код? Это выглядит таинственно, но в этом посте вы можете получить представление, что же происходит под капотом.
Начнём наше путешествие в язык с экскурсии в удивительный мир JavaScript-движков.
Откройте консоль в Chrome и перейдите на вкладку Sources. Вы увидите несколько разделов, и один из самых интересных называется Call Stack (в Firefox вы увидите Call Stack, когда поставите брейкпоинт в коде):
Что такое Call Stack? Похоже, тут много чего происходит, даже ради исполнения пары строк кода. На самом деле JavaScript не поставляется в коробке с каждым браузером. Существует большой компонент, который компилирует и интерпретирует наш JavaScript-код — это JavaScript-движок. Самыми популярными являются V8, он используется в Google Chrome и Node.js, SpiderMonkey в Firefox, JavaScriptCore в Safari/WebKit.
Сегодня JavaScript-движки представляют собой прекрасные образцы программной инженерии, и будет практически невозможно рассказать обо всех аспектах. Однако основную работу по исполнению кода делают для нас лишь несколько компонентов движков: Call Stack (стек вызовов), Global Memory (глобальная память) и Execution Context (контекст исполнения). Готовы с ними познакомиться?
1. JavaScript-движки и глобальная память
Я говорил, что JavaScript является одновременно компилируемым и интерпретируемым языком. Хотите верьте, хотите нет, но на самом деле JavaScript-движки компилируют ваш код за микросекунды до его исполнения.
Волшебство какое-то, да? Это волшебство называется JIT (Just in time compilation). Она сама по себе является большой темой для обсуждения, даже книги будет мало, чтобы описать работу JIT. Но пока что мы пропустим теорию и сосредоточимся на фазе исполнения, которая не менее интересна.
Для начала посмотрите на этот код:
Допустим, я спрошу вас, как этот код обрабатывается в браузере? Что вы ответите? Вы можете сказать: «браузер читает код» или «браузер исполняет код». В реальности всё не так просто. Во-первых, код считывает не браузер, а движок. JavaScript-движок считывает код, и как только он определяет первую строку, то кладёт пару ссылок в глобальную память.
Глобальная память (которую также называют кучей (heap)) — это область, в которой JavaScript-движок хранит переменные и объявления функций. И когда он прочитает приведённый выше код, то в глобальной памяти появятся два биндинга:
В данный момент ничего не исполняется. Давайте теперь попробуем исполнить нашу функцию:
Что произойдёт? А произойдёт кое-что интересное. При вызове функции JavaScript-движок выделит два раздела:
2. JavaScript-движки: как они работают? Глобальный контекст исполнения и стек вызовов
Вы узнали, как JavaScript-движок читает переменные и объявления функций. Они попадают в глобальную память (кучу).
Но теперь мы исполняем JavaScript-функцию, и движок должен об этом позаботиться. Каким образом? У каждого JavaScript-движка есть ключевой компонент, который называется стек вызовов.
Это стековая структура данных: элементы могут добавляться в неё сверху, но они не могут исключаться из структуры, пока над ними есть другие элементы. Именно так устроены JavaScript-функции. При исполнении они не могут покинуть стек вызовов, если в нём присутствует другая функция. Обратите на это внимание, поскольку эта концепция помогает понять утверждение «JavaScript является однопоточным».
Но вернёмся к нашему примеру. При вызове функции движок отправляет её в стек вызовов:
Мне нравится представлять стек вызовов в виде стопки чипсов Pringles. Мы не можем съесть чипс снизу стопки, пока не съедим те, что лежат сверху. К счастью, наша функция является синхронной: это всего лишь умножение, которое быстро вычисляется.
В то же самое время движок размещает в памяти глобальный контекст исполнения, это глобальная среда, в которой исполняется JavaScript-код. Вот как это выглядит:
Представьте глобальный контекст исполнения в виде моря, в котором глобальные JavaScript-функции плавают, словно рыбы. Как мило! Но это лишь половина всей истории. Что, если наша функция имеет вложенные переменные или внутренние функции?
Даже в простом случае, как показано ниже, JavaScript-движок создаёт локальный контекст исполнения:
Рядом с pow появится локальный контекст исполнения, внутри зелёного раздела-прямоугольника, расположенного внутри глобального контекста исполнения. Представьте также, как для каждой вложенной функции внутри вложенной функции движок создаёт другие локальные контексты исполнения. Все эти разделы-прямоугольники появляются очень быстро! Как матрёшка!
Давайте теперь вернёмся к истории с однопоточностью. Что это означает?
3. JavaScript является однопоточным, и другие забавные истории
Мы говорим, что JavaScript является однопоточным, потому что наши функции обрабатывает лишь один стек вызовов. Напомню, что функции не могут покинуть стек вызовов, если исполнения ожидают другие функции.
Это не проблема, если мы работаем с синхронным кодом. К примеру, сложение двух чисел является синхронным и вычисляется за микросекунды. А что насчёт сетевых вызовов и других взаимодействий с внешним миром?
К счастью, JavaScript-движки спроектированы так, чтобы по умолчанию работать асинхронно. Даже если они могут исполнять только по одной функции за раз, более медленные функции могут исполняться внешней сущностью — в нашем случае это браузер. Об этом мы поговорим ниже.
В то же время вы знаете, что когда браузер загружает какой-то JavaScript-код, движок считывает этот код строка за строкой и выполняет следующие шаги:
4. Асинхронный JavaScript, очередь обратных вызовов и цикл событий
Благодаря глобальной памяти, контексту исполнения и стеку вызовов синхронный JavaScript-код исполняется в наших браузерах. Но мы кое о чём забыли. Что происходит, если нужно исполнить какую-нибудь асинхронную функцию?
Под асинхронной функцией я подразумеваю каждое взаимодействие с внешним миром, для завершения которого может потребоваться какое-то время. Вызов REST API или таймера — асинхронны, потому что на их выполнение могут уйти секунды. Благодаря имеющимся в движке элементам мы можем обрабатывать такие функции без блокирования стека вызовов и браузера. Не забывайте, стек вызовов может исполнять одновременно только одну функцию, и даже одна блокирующая функция может буквально остановить браузер. К счастью, JavaScript-движки «умны», и с небольшой помощью браузера могут такие вещи отсортировывать.
Когда мы исполняем асинхронную функцию, браузер берёт её и выполняет для нас. Возьмём такой таймер:
Через 10 секунд браузер берёт callback-функцию, которую мы ему передали, и кладёт её в очередь обратных вызовов. В данный момент в JavaScript-движке появилось ещё два раздела-прямоугольника. Посмотрите на этот код:
Теперь наша схема выглядит так:
setTimeout исполняется внутри контекста браузера. Через 10 секунд таймер запускается и callback-функция готова к исполнению. Но для начала она должна пройти через очередь обратных вызовов. Это структура данных в виде очереди, и, как свидетельствует её название, представляет собой упорядоченную очередь из функций.
Каждая асинхронная функция должна пройти через очередь обратных вызовов, прежде чем попасть в стек вызовов. Но кто отправляет функции дальше? Это делает компонент под названием цикл событий.
Пока что цикл событий занимается только одним: проверяет, пуст ли стек вызовов. Если в очереди обратных вызовов есть какая-нибудь функция и если стек вызовов свободен, тогда пора отправлять callback в стек вызовов.
После этого функция считается исполненной. Так выглядит общая схема обработки асинхронного и синхронного кода JavaScript-движком:
Помните: браузерные API, очередь обратных вызовов и цикл событий являются столпами асинхронного JavaScript.
И если интересно, можете посмотреть любопытное видео «What the heck is the event loop anyway» Филипа Робертса. Это одно из лучших объяснений цикла событий.
Но мы ещё не закончили с темой асинхронного JavaScript. В следующих главах мы рассмотрим ES6-промисы.
5. Callback hell и ES6-промисы
Callback-функции используются в JavaScript везде, и в синхронном, и в асинхронном коде. Рассмотрим этот метод:
Термин Callback hell в JavaScript применяют к «стилю» программирования, при котором callback’и вкладывают в другие callback’и, которые вложены в другие callback’и… Из-за асинхронной природы JavaScript-программисты уже давно попадают в эту ловушку.
Если честно, я никогда не создавал большие пирамиды callback’ов. Возможно, потому что я ценю читабельный код и всегда стараюсь придерживаться его принципов. Если вы попали в callback hell, это говорит о том, что ваша функция делает слишком много.
Я не буду подробно говорить о callback hell, если вам интересно, то сходите на сайт callbackhell.com, там эта проблема подробно исследована и предложены разные решения. А мы поговорим о ES6-промисах. Это аддон к JavaScript, призванное решить проблему ада обратных вызовов. Но что такое «промисы»?
Промис в JavaScript — это представление будущего события. Промис может завершиться успешно, или на жаргоне программистов промис будет «разрешён» (resolved, исполнен). Но если промис завершается с ошибкой, то мы говорим, что он в состоянии «отклонён» (rejected). Также у промисов есть состояние по умолчанию: каждый новый промис начинается в состоянии «ожидания решения» (pending). Можно ли создать собственный промис? Да. Об этом мы поговорим в следующей главе.
6. Создание и работа с JavaScript-промисами
Как видите, resolve — это функция, которую мы вызываем, чтобы промис успешно завершился. А reject создаст отклонённый промис:
Сейчас промисы не выглядят такими полезными, верно? Эти примеры ничего не выводят для пользователя. Давайте кое-что добавим. И разрешённые, от отклонённые промисы могут возвращать данные. Например:
Как JavaScript-разработчик и потребитель чужого кода вы по большей части взаимодействуете с внешними промисами. Создатели библиотек чаще всего обёртывают legacy-код в конструктор промисов, таким образом:
И при необходимости мы также можем создать и разрешить промис, вызвав Promise.resolve() :
7. Обработка ошибок в ES6-промисах
Обрабатывать ошибки в JavaScript всегда было просто, как минимум в синхронном коде. Взгляните на пример:
К счастью, с промисами мы можем обрабатывать асинхронные ошибки, словно они синхронные. В прошлой главе я говорил, что вызов reject приводит к отклонению промиса:
Кроме того, чтобы для создания и отклонения промиса в нужном месте можно вызывать Promise.reject() :
8. Комбинаторы ES6-промисов: Promise.all, Promise.allSettled, Promise.any и другие
Промисы не предназначены для работы по одиночке. Promise API предлагает ряд методов для комбинирования промисов. Один из самых полезных — Promise.all, он берёт массив из промисов и возвращает один промис. Только проблема в том, что Promise.all отклоняется, если отклонен хотя бы один промис в массиве.
Promise.race разрешает или отклоняет, как только один из промисов в массиве получает соответствующий статус.
9. ES6-промисы и очередь микрозадач
Если помните из предыдущей главы, каждая асинхронная callback-функция в JavaScript оказывается в очереди обратных вызовов, прежде чем попадает в стек вызовов. Но у callback-функций, переданных в промис, иная судьба: они обрабатываются очередью микрозадач (Microtask Queue), а не очередью задач.
И здесь вам нужно быть внимательными: очередь микрозадач предшествует очереди вызовов. Обратные вызовы из очереди микрозадач имеют приоритет, когда цикл событий проверяет, готовы ли новые callback’и перейти в стек вызовов.
Подробнее эта механика описана Джейком Арчибальдом в Tasks, microtasks, queues and schedules, замечательное чтиво.
10. JavaScript-движки: как они работают? Асинхронная эволюция: от промисов до async/await
async/await — всего лишь стилистическое улучшение, которое мы называем синтаксическим сахаром. async/await никак не меняет JavaScript (не забывайте, язык должен быть обратно совместим со старыми браузерами и не должен ломать существующий код). Это лишь новый способ написания асинхронного кода на основе промисов. Рассмотрим пример. Выше мы уже сохранили промис в соответствующем then :
Выглядит здраво, верно? Забавно, что async-функция всегда возвращает промис, и никто не может ей в этом помешать:
Давайте снова взглянем на промис, в котором мы обрабатываем ошибки с помощью обработчика catch :
С асинхронными функциями мы можем отрефакторить вот так:
Однако ещё не все перешли на этот стиль. try/catch может усложнить ваш код. При этом нужно учитывать ещё кое-что. Посмотрите, как в этом коде возникает ошибка внутри блока try :
Помимо этого async/await выглядит лучшим способом структурирования асинхронного кода в JavaScript. Мы лучше управляем обработкой ошибок и код выглядит чище.
11. JavaScript-движки: как они работают? Итоги
JavaScript — это скриптовый язык для веба, он сначала компилируется, а затем интерпретируется движком. Самые популярные JS-движки: V8, применяется в Google Chrome и Node.js; SpiderMonkey, разработан для Firefox; JavaScriptCore, используется в Safari.
JavaScript-движки имеют много «движущихся» частей: стек вызовов, глобальная память, цикл событий, очередь обратных вызовов. Все эти части идеально работают вместе, обеспечивая обработку синхронного и асинхронного кода.
JavaScript-движки являются однопоточными, то есть для исполнения функций применяется единственный стек вызовов. Это ограничение лежит в основе асинхронной природы JavaScript: все операции, для выполнения которых требуется какое-то время, должны управляться внешней сущностью (например, браузером) или функцией обратного вызова.
Для упрощения работы асинхронного кода в ECMAScript 2015 были внедрены промисы. Промис — это асинхронный объект, используемый для представления успешности или неуспешности любой асинхронной операции. Но улучшения на этом не прекратились. В 2017-м появились async/await : стилистическое улучшение для промисов, позволяющее писать асинхронный код, как если бы он был синхронным.






