Асинхронный JavaScript: изучаем Async/Await, Callbacks и Promises
Рассказываем, как построить приложение с Async/Await на JavaScript. Также объясняем, как работают Callbacks и Promises.
11К открытий14К показов
Сегодня мы попытаемся построить и запустить магазин мороженого, при этом одновременно изучить асинхронный JavaScript. Также вы узнаете, как использовать:
- Callbacks.
- Promises.
- Async / Await.
Вот что мы рассмотрим в этой статье:
- Что такое асинхронный JavaScript.
- Синхронный и асинхронный JavaScript.
- Как работают Callbacks в JavaScript.
- Как работают Promises в JavaScript.
- Как работает Async / Await в JavaScript.
Это — перевод оригинальной статьи на freeCodeCamp от автора Joy Shaheb.
Ещё у нас есть видеоверсия урока от автора (осторожно, английский язык):
Итак, давайте начнём!
Что такое асинхронный JavaScript
Если вы хотите разрабатывать эффективные проекты, то эта концепция идеально подходит вам.
Теория асинхронного JavaScript помогает разбивать большие сложные проекты на более мелкие задачи.
Вы можете использовать любую из этих трех техник – Callbacks, Promises или Async/await – для выполнения небольших задач таким образом, чтобы получить наилучшие результаты.
Синхронный и асинхронный JavaScript
Что такое синхронная система
В синхронной системе задачи выполняются одна за другой.
Представьте, что у вас всего одна рука для выполнения 10 задач. Из-за этого вы должны выполнять по одной задаче за один раз.
Посмотрите на GIF, здесь происходит одно действие за раз:
Вы увидите, что пока первое изображение не загрузится полностью, второе изображение не начнет загружаться.
По умолчанию JavaScript является синхронным или однопоточным. Подумайте об этом так: один поток означает одну руку, которой можно что-то делать.
Что такое асинхронная система
В этой системе задачи выполняются независимо друг от друга.
Представьте, что для выполнения 10 задач у вас есть 10 рук. Таким образом, каждая рука может выполнять задачу независимо друг от друга и одновременно.
Посмотрите на GIF. Вы увидите, что каждое изображение загружается одновременно.
Все изображения загружаются в своем собственном темпе. Ни одно из них не ждёт загрузки других.
Итак, в чём разница между синхронным и асинхронным JS
В синхронной системе изображения находятся в одном потоке обработки. Одно изображение не может обогнать другое. Загрузка завершается по очереди. Если первое изображение заканчивает загрузку, останавливается и следующее изображение.
В асинхронной системе изображения находятся на разных дорожках. Они закончат загрузку в своем собственном темпе. Никто ни перед кем не останавливается, если вдруг возникнет ошибка.
Примеры синхронного и асинхронного кода
Прежде чем начать наш проект, давайте рассмотрим несколько примеров.
Пример синхронного кода
Чтобы протестировать синхронную систему, напишите этот код на JavaScript:
Вот результат в консоли:
Пример асинхронного кода
Допустим, чтобы съесть мороженое, требуется две секунды. Теперь давайте протестируем асинхронную систему. Напишите приведенный ниже код на JavaScript.
Примечание: Не волнуйтесь, функцию setTimeout()
мы обсудим позже.
И вот результат в консоли:
Теперь, когда вы знаете разницу между синхронными и асинхронными операциями, давайте создадим наш магазин мороженого.
Как настроить наш проект
Для этого проекта вы можете просто открыть Codepen.io и начать кодить. Или вы можете сделать это в VS code или другом редакторе по вашему выбору.
Откройте раздел JavaScript, а затем откройте консоль разработчика. Мы напишем наш код и посмотрим результаты в консоли.
Что такое обратные вызовы в JavaScript
Когда вы вкладываете функцию в другую функцию в качестве аргумента, это называется обратным вызовом или callback.
Вот иллюстрация обратного вызова:
Не волнуйтесь, через минуту мы увидим несколько примеров обратных вызовов.
Зачем использовать обратные вызовы
При выполнении сложной задачи мы разбиваем ее на более мелкие шаги. Чтобы установить связь между этими шагами по времени (необязательно) и порядку, мы используем обратные вызовы.
Взгляните на этот пример:
Вот те небольшие шаги, которые необходимо сделать, чтобы приготовить мороженое.
Также обратите внимание, что в этом примере порядок действий и время имеют решающее значение. Вы не можете просто нарезать фрукты и подать мороженое.
В то же время, если предыдущий шаг не завершен, мы не можем перейти к следующему.
Магазин будет состоять из двух частей:
- В кладовой будут храниться все ингредиенты [Бэкенд].
- Производить мороженое мы будем на нашей кухне [Фронтенд].
Создаём и сохраняем данные
Теперь давайте создадим наши ингредиенты, то есть фрукты, внутри объекта.
Дополним наши ингредиенты вафельными стаканчиками, топпингами и прочим:
Весь бизнес зависит от того, что заказывает клиент. Как только мы получаем заказ, мы начинаем производство, а затем подаем мороженое. Итак, мы создадим две функции:
- order;
- production.
Вот как все это работает:
Давайте создадим наши функции. Здесь мы будем использовать стрелочные функции:
Теперь давайте установим связь между этими двумя функциями с помощью обратного вызова, например, так:
Давайте проведем небольшой тест
Мы будем использовать функцию console.log()
для проведения тестов, чтобы развеять все сомнения, которые могут возникнуть относительно того, как мы установили связь между двумя функциями.
Чтобы запустить тест, мы вызовем функцию order
. И добавим в качестве аргумента вторую функцию с именем production
.
Вот результат в нашей консоли:
Сохраните этот код и удалите все, но не удаляйте нашу переменную stocks.
В первой функции передайте еще один аргумент, чтобы мы могли получить заказ с названием нужного фрукта:
Обозначим последовательность шагов и нужное время для их выполнения.
На этой схеме видно, что шаг 1 – размещение заказа, который занимает 2 секунды. Затем шаг 2 – нарезать фрукты (2 секунды), шаг 3 – добавить воду и лед (1 секунда), шаг 4 – запустить машину (1 секунда), шаг 5 – выбрать контейнер (2 секунды), шаг 6 – выбрать начинку (3 секунды) и шаг 7 – подать мороженое, что занимает 2 секунды.
Для определения нужного времени отлично подходит функция setTimeout()
, так как она использует callback
, принимая функцию в качестве аргумента.
Теперь давайте выберем наши фрукты и воспользуемся этой функцией:
Вот результат в консоли. Обратите внимание, что результат отображается через 2 секунды.
Если вам интересно, как мы выбрали именно клубнику, вот код с форматом:
Ничего не удаляйте. Теперь мы начнем писать функцию для производства мороженого. Мы опять будем использовать стрелочные функции:
Результат:
Добавим еще одну функцию в setTimeout
, чтобы нарезать фрукты.
Результат:
Давайте завершим полный процесс производства мороженого, вложив новые функции внутрь существующих функций – это и есть обратный вызов, помните?
Смущены тем, как всё выглядит? Это ад обратных вызовов. ? Они всегда выглядят примерно вот так:
Как же сделать так, чтобы код выглядел красивым?
Как использовать Promises, чтобы избежать ада обратного вызова
Promises были изобретены для избавления от ада обратных вызовов и для лучшей обработки наших задач.
Вот, как они выглядят:
Разберём, как они работают.
Как показано на графиках выше, promise имеет три состояния:
- Ожидание. Это начальная стадия. Здесь ничего не происходит. Подумайте об этом так: ваш клиент не торопится делать заказ. Он ещё ничего не заказал.
- Решено. Это означает, что ваш клиент получил свою еду и доволен.
- Отклонено. Это означает, что ваш клиент не получил свой заказ и покинул ресторан.
Давайте применим обещания в нашем примере с производством мороженого.
Сначала нам нужно понять еще четыре вещи:
- Взаимосвязь между временем и работой.
- Как работает цепочка Promise.
- Как работает обработка ошибок.
- Как работает обработчик .finally
Разберем каждую из этих концепций по очереди, делая небольшие шаги.
Взаимосвязь между временем и работой
Если вы помните, это наши шаги и время, которое занимает каждый шаг, чтобы сделать мороженое.
Чтобы это произошло, давайте создадим переменную в JavaScript:
Теперь создайте функцию с именем order
и передайте ей два аргумента с именами time
, work
:
Теперь мы дадим обещание (Promise) нашему клиенту: “Мы подадим вам мороженое”:
Наше обещание состоит из 2 частей:
- Решено [мороженое доставлено].
- Отклонено [клиент не получил мороженое].
То есть Promise состоит из Resolve и Reject.
Давайте добавим коэффициенты времени и работы внутри нашего promise
с помощью функции setTimeout()
внутри оператора if
.
Примечание: В реальной жизни вы можете обойтись и без фактора времени. Это зависит от требований проекта.
Теперь мы используем нашу только что созданную функцию для запуска производства.
Результат после двух секунд ожидания:
Цепочки обещаний
В этом методе мы определяем, что нам нужно сделать, когда первая задача будет выполнена, используя обработчик .then
.
Обработчик .then
возвращает promise, когда наше первое обещание будет разрешено.
Такой механизм похож на то, как вы даете кому-то инструкции. Вы говорите кому-то: “Сначала сделай это, потом сделай то, потом другое, потом…, потом…, потом…”, и так далее.
- Первая задача – это первоначальный Promise.
- Остальные задачи возвращают наше обещание после того, как будет выполнена одна небольшая часть работы.
Давайте реализуем это в нашем проекте.
Примечание: не забудьте написать слово return
внутри обработчика .then
. В противном случае он не будет работать должным образом. Если вам интересно, зачем он нужен, попробуйте удалить return
, когда мы закончим все шаги:
Вот результат:
Используя ту же систему, закончим наш проект.
Результат:
Обработка ошибок
Нам нужен способ обработки ошибок, когда что-то идет не так. Чтобы отловить ошибки, давайте изменим нашу переменную на false
.
Это означает, что наш магазин закрыт. Мы больше не продаем мороженое нашим клиентам.
Чтобы справиться с ошибкой, мы используем обработчик .catch
. Как и .then
, он также возвращает Promise, но только в том случае, если наше первоначальное обещание отклонено.
Итак, напоминаем:
- .then работает, когда Promise сработал как следует.
- .catch работает, когда Promise отвергнут.
Таким образом, между предыдущим обработчиком .then
и обработчиком .catch
не должно быть вообще ничего.
Результат:
Что значит вывод после исполнения кода:
- Первое сообщение приходит от части reject() нашего Promise.
- Второе сообщение приходит из обработчика .catch.
Как использовать обработчик .finally()
Существует обработчик finally, который работает независимо от того, был ли наш Promise выполнен или отклонен.
Например: независимо от того, обслужили ли мы хотя бы одного клиента или 100 клиентов, наш магазин закроется в конце дня.
Вот пример такого кода:
Результат:
Как работает Async / Await в JavaScript
Предполагается, что это лучший способ написания Promise, который помогает нам сохранять код простым и чистым.
Все, что вам нужно сделать, это написать слово async
перед любой обычной функцией, и она станет обещанием.
Promises против Async/Await на JavaScript
До появления async/await для выполнения обещания мы писали следующее:
Теперь используем async:
Как использовать ключевые слова Try и Catch
Мы используем try
для выполнения кода, а catch
– для отлова ошибок. Это та же концепция, которую мы видели при рассмотрении обещаний.
Давайте сравним, как это работает.
В обещаниях мы использовали resolve
и reject
:
Когда мы используем async/await, мы используем этот формат:
Теперь, надеемся, вы понимаете разницу между Promises и Async/Await.
Как использовать Await в JavaScript
Ключевое слово await
заставляет JavaScript ждать, пока Promise выполнится и вернет результат.
Вернемся к нашему магазину мороженого. Мы не знаем, какой топпинг предпочтет покупатель: шоколад или арахис. Нам нужно остановить машину и спросить клиента, чего бы он хотел.
Заметьте, что мы остановили только кухню, но персонал вне кухни продолжает делать такие вещи, как:
- мытье посуды;
- уборка столов;
- приём заказов, и т.д.
Давайте создадим Promise, чтобы спросить, какой топпинг использовать. Процесс занимает три секунды.
Теперь давайте сначала создадим функцию для кухни с async
.
Давайте добавим другие задачи ниже kitchen()
.
Вот результат:
Мы буквально выходим из кухни, чтобы спросить клиента: “Какой топпинг вы предпочитаете?”. В это время другие задачи не заканчиваются.
Как только мы узнаем о выборе топпинга, мы возвращаемся на кухню и заканчиваем заказ.
При использовании Async/Await вы также можете использовать обработчики .then
, .catch
и .finally
, которые являются основной частью Promises.
Запускаем магазин мороженого
Для этого создадим две функции:
- kitchen для приготовления мороженого;
- time для назначения количества времени, которое займет каждая небольшая задача.
Сначала создадим функцию time
:
Теперь создадим кухню:
Проверим, работает ли наша кухня:
Вот, что вы должны увидеть, если магазин считается открытым:
А вот, что будет, если магазин закрыт.
Завершим наш проект. Для этого вспомним, какие задачи должны выполняться в магазине:
Открываем магазин:
Добавляем все функции для нашей кухни:
Результат:
Заключение
Поздравляем, вы дочитали до конца! В этой статье вы изучили:
- Разницу между синхронными и асинхронными системами.
- Механизмы асинхронного JavaScript с использованием обратных вызовов, обещаний и Async/Await.
Благодарим за то, что дочитали до конца!
11К открытий14К показов