Курс →React JS

для начинающих
от платформы StackDev.ru

Async Await в Javascript (как работает и примеры)

Дата: 2020-09-27
Javascript

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

В свое время “промисы” (объекты promise) сильно облегчили нашу жизнь, избавив от написания асинхронных запросов с использованием коллбэков. “Промисы” также используются конструкцией async wait, которым посвящена эта статья.

Объекты promise можно представить себе, как что-то, что мы получаем не сразу, а через какое-то время.

Примеры таких объектов:

  • AJAX запросы на получение данных
  • или получение доступа к web камере пользователя, после клика на кнопку “разрешить”

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

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

Пример 1.

Представим, что приготовление нашего завтрака разбито на действия:

  • Готовим кофе
  • Пьем кофе
  • Готовим тосты
  • Едим тосты
  • Моем посуду

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

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

Во времена 'коллбэков', реализация нашего примера имела бы следующий вид (это – то, что называется callback hell):

1makeCoffee(function()) {
2 makeToast(function()) {
3 drinkCoffeel(function()) {
4 eatToast(function()) {
5 washDishes(function()) {
6 // Закончили завтрак
7 }
8 }
9 }
10 }
11}

Промисы позволяют написать тоже самое более просто и понятно. Мы запускаем нужные нам процессы и получаем в ответ обещания (объекты Promise).

1const coffeePromise = makeCoffe();
2const breakfastPromise = makeBreakfast();

Через какое-то время получаем результат (запрашиваемые данные). По мере получения результатов (данных) наших действий, продолжаем завтракать (пить кофе и есть тосты).

1// Промис на чашку кофе
2coffeePromise.then((response) => {
3 // Пьем готовый кофе
4 drinkCoffee();
5});
6
7// Промис на тосты
8toastPromise.then((response) => {
9 // Едим готовые тосты
10 eatToast();
11});

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

1const coffeePromise = makeCoffe();
2const breakfastPromise = makeBreakfast();
3
4Promise.all([coffeePromise, toastPromise]).then(([coffee, toast]) => {
5 // Пьем готовый кофе и едим тосты
6 eatDrink(coffee, toast);
7});

Мы живем в мире промисов. Например, большое количество API, доступных в браузере используют объекты promise:

Метод fetch() - возвращает promise:

1fetch('https://github.com/vasilymur')
2 .then((data) => data.json())
3 .then((vasily) => console.log(vasily));

Асинхронные запросы можно также выполнять с помощью библиотеки Axios, которая работает в браузере и также возвращает promise.

1axios.get('https://github.com/vasilymur').then((vasily) => console.log(vasily));

Мы также можем легко создать наши собственные объекты promise.

1const getWeather = () => {
2 return new Promise((resolve, reject) => {
3 setTimeout(() => {
4 resolve('Сегодня 28 Градусов!!');
5 }, 3000);
6 });
7};
8
9const curWeather = getWeather();
10curWeather.then((data) => {
11 // Получаем данные через 3 сек.
12 console.log(data);
13});

Промисы кажутся отличным решением для работы с асинхронным кодом, но есть несколько моментов, которые мне не нравятся:

  • Приходится использовать слово then
  • Весь код, завязанный на данные, которые мы получаем из “промиса” должен быть помещен внутри конструкции .then()

Async Await JS

Конструкция async await также использует объекты promise для создания асинхронных запросов, но делает написание нашего кода заметно проще.

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

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

1async function eatBreakfast() {}

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

1// 1-й промис
2const getToast = () => {
3 return new Promise((resolve, reject) => {
4 setTimeout(() => {
5 resolve('Ваш тост готов!');
6 }, 1000);
7 });
8};
9
10// 2-й промис
11const getCoffee = () => {
12 return new Promise((resolve, reject) => {
13 setTimeout(() => {
14 resolve('Ваш кофе готов!');
15 }, 2000);
16 });
17};
18
19// Ставим слово async
20const breakfast = async function () {
21 // Указываем await
22 const toast = await getToast();
23 // Указываем await
24 const coffee = await getCoffee();
25
26 // Получаем результат обоих промисов
27 const [myToast, myCoffee] = await Promise.all([toast, coffee]);
28 console.log(myToast, myCoffee);
29 // Ваш тост готов! Ваш кофе готов! (через 2сек)
30};
31
32breakfast();

Async Await: Обработка Ошибок

Чтобы обрабатывать ошибки в функциях с async await используется конструкция try catch. Необходимо обернуть весь код, содержащийся в функции внутрь try {…} и ловить возможные ошибки внутри catch (err) {…}.

1const breakfast = async function () {
2 try {
3 const toast = await getToast();
4 const coffee = await getCoffee();
5
6 const [myToast, myCoffee] = await Promise.all([toast, coffee]);
7 console.log(myToast, myCoffee);
8 } catch (err) {
9 // Обрабатываем ошибки
10 console.log(err);
11 }
12};

Еще один вариант обработки ошибок в async await'добавлять' .catch к каждой отдельной функции, возвращающей промис.

1async function loadCities() {
2 const response = await fetch(
3 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/ities.json'
4 );
5 const cities = await response.json();
6 console.log(cities);
7}
8
9loadCities().catch((err) => {
10 console.log('Ошибка! ', err);
11});

Более продвинутый вариант – вынести логику catch во внешнюю функцию (Higher Order Function).

Возьмем нашу функцию, которая обращается к внешнему API, для получения данных о городах:

1async function loadCities() {
2 const response = await fetch(
3 // адрес с ошибкой (citis вместо cities)
4 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/citis.json'
5 );
6 const cities = await response.json();
7}

Далее пишем функцию, которая будет принимать нашу функцию и возвращать ее же, но с добавленным catch, как в предыдущем примере:

1function handleError(fn) {
2 console.log(fn);
3 return function () {
4 return fn().catch(function (err) {
5 console.log('Ошибка!!', err);
6 });
7 };
8}

Далее мы можем использовать следующую конструкцию, чтобы обернуть нашу функцию loadCities() в функцию handleError():

1const getCitiesWithErrorHandler = handleError(loadCities);
2getCitiesWithErrorHandler();

Таким образом мы сделаем наш код более простым и менее громоздким.