Async Await в 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});67// Промис на тосты8toastPromise.then((response) => {9 // Едим готовые тосты10 eatToast();11});
Можно дождаться пока получим ответ для обоих объектов promise
, и продолжить завтрак с уже приготовленным кофе и тостом.
1const coffeePromise = makeCoffe();2const breakfastPromise = makeBreakfast();34Promise.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};89const 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};910// 2-й промис11const getCoffee = () => {12 return new Promise((resolve, reject) => {13 setTimeout(() => {14 resolve('Ваш кофе готов!');15 }, 2000);16 });17};1819// Ставим слово async20const breakfast = async function () {21 // Указываем await22 const toast = await getToast();23 // Указываем await24 const coffee = await getCoffee();2526 // Получаем результат обоих промисов27 const [myToast, myCoffee] = await Promise.all([toast, coffee]);28 console.log(myToast, myCoffee);29 // Ваш тост готов! Ваш кофе готов! (через 2сек)30};3132breakfast();
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();56 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}89loadCities().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();
Таким образом мы сделаем наш код более простым и менее громоздким.