Любое современное приложение подразумевает создание и обработку большого количества асинхронных запросов для получения и отправки различных данных.
В свое время “промисы” (объекты promise) сильно облегчили нашу жизнь, избавив от написания асинхронных запросов с использованием коллбэков. “Промисы” также используются конструкцией async wait, которым посвящена эта статья.
Объекты promise можно представить себе, как что-то, что мы получаем не сразу, а через какое-то время.
Примеры таких объектов:
AJAX запросы на получение данных или получение доступа к web камере пользователя, после клика на кнопку “разрешить” При этом весь последующий код не блокируется в ожидании завершения выполнения асинхронного запроса, а продолжает выполняться в обычном режиме.
Весь код, завязанный на получаемые в асинхронном запросе данные, продолжает выполняться только после получения этих данных.
Пример 1.
Представим, что приготовление нашего завтрака разбито на действия:
Мы выполняем каждое действие без жесткой последовательности. Например, начинаем пить кофе пока готовятся наши тосты. То есть, приготовление тостов не блокирует нашу возможность начать пить кофе.
В нашем случае приготовление тостов - промис, результат которого мы получим, когда сработает таймер на тостере.
Во времена 'коллбэков', реализация нашего примера имела бы следующий вид (это – то, что называется callback hell):
```Plain Text makeCoffee(function()) { makeToast(function()) { drinkCoffeel(function()) { eatToast(function()) { washDishes(function()) { // Закончили завтрак } } } } }
**Промисы** позволяют написать тоже самое более **просто** и понятно. Мы **запускаем** нужные нам процессы и получаем в ответ **обещания** (объекты Promise).
Plain Text const coffeePromise = makeCoffe(); const breakfastPromise = makeBreakfast();
Через **какое-то время** получаем результат (запрашиваемые данные). **По мере** получения результатов (данных) наших действий, **продолжаем** завтракать (пить кофе и есть тосты).
Plain Text // Промис на чашку кофе coffeePromise.then((response) => { // Пьем готовый кофе drinkCoffee(); });
// Промис на тосты toastPromise.then((response) => { // Едим готовые тосты eatToast(); });
Можно дождаться пока **получим** ответ для **обоих** объектов `promise`, и продолжить завтрак с уже приготовленным **кофе** и **тостом**.
Plain Text const coffeePromise = makeCoffe(); const breakfastPromise = makeBreakfast();
Promise.all([coffeePromise, toastPromise]).then(([coffee, toast]) => { // Пьем готовый кофе и едим тосты eatDrink(coffee, toast); });
Мы живем в мире **промисов**. Например, большое количество **API**, доступных в **браузере** используют объекты **promise**:
Метод `fetch()` - возвращает **promise**
Plain Text fetch('https://github.com/vasilymur') .then((data) => data.json()) .then((vasily) => console.log(vasily));
**Асинхронные** запросы можно также выполнять с помощью библиотеки **Axios**, которая работает в браузере и также возвращает **promise**.
Plain Text axios.get('https://github.com/vasilymur').then((vasily) => console.log(vasily));
Мы также можем легко **создать** наши собственные объекты **promise**.
Plain Text const getWeather = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('Сегодня 28 Градусов!!'); }, 3000); }); };
const curWeather = getWeather(); curWeather.then((data) => { // Получаем данные через 3 сек. console.log(data); });
**Промисы** кажутся **отличным** решением для работы с **асинхронным** кодом, но есть несколько моментов, которые мне **не нравятся**:
- **Приходится** использовать слово `then`
- Весь **код**, завязанный на данные, которые мы **получаем** из “промиса” должен быть помещен **внутри** конструкции `.then()`
## Async Await JS
Конструкция `async await` также использует объекты **promise** для создания **асинхронных** запросов, но делает написание нашего кода заметно **проще**.
**Async await** позволяет писать **асинхронный** код, так как будто он является **синхронным**.
Для использования `async await` нужно указать, что наша функция будет содержать асинхронный код, путем добавления слова `async`.
Plain Text async function eatBreakfast() {}
Далее, **внутри** функции нужно **отметить** словом `await` те строчки, в которых содержится **асинхронный** код.
Plain Text // 1-й промис const getToast = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('Ваш тост готов!'); }, 1000); }); };
// 2-й промис const getCoffee = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('Ваш кофе готов!'); }, 2000); }); };
// Ставим слово async const breakfast = async function () { // Указываем await const toast = await getToast(); // Указываем await const coffee = await getCoffee();
// Получаем результат обоих промисов const [myToast, myCoffee] = await Promise.all([toast, coffee]); console.log(myToast, myCoffee); // Ваш тост готов! Ваш кофе готов! (через 2сек) };
breakfast();
## Async Await: Обработка Ошибок
Чтобы обрабатывать **ошибки** в функциях с `async await` используется конструкция `try catch`. Необходимо **обернуть** весь код, содержащийся в функции внутрь `try {…}` и ловить возможные **ошибки** внутри `catch (err) {…}`.
Plain Text const breakfast = async function () { try { const toast = await getToast(); const coffee = await getCoffee();
const [myToast, myCoffee] = await Promise.all([toast, coffee]);
console.log(myToast, myCoffee);
} catch (err) { // Обрабатываем ошибки console.log(err); } };
Еще один вариант **обработки ошибок** в `async await` – **'добавлять'** `.catch` к каждой **отдельной** функции, возвращающей **промис**.
Plain Text async function loadCities() { const response = await fetch( 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/ities.json' ); const cities = await response.json(); console.log(cities); }
loadCities().catch((err) => { console.log('Ошибка! ', err); });
Более **продвинутый** вариант – вынести логику `catch` во **внешнюю** функцию (Higher Order Function).
Возьмем нашу **функцию**, которая обращается к **внешнему** API, для получения данных о городах:
Plain Text async function loadCities() { const response = await fetch( // адрес с ошибкой (citis вместо cities) 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/citis.json' ); const cities = await response.json(); }
Далее пишем **функцию**, которая будет **принимать** нашу функцию и возвращать ее же, но с **добавленным** `catch`, как в предыдущем примере:
Plain Text function handleError(fn) { console.log(fn); return function () { return fn().catch(function (err) { console.log('Ошибка!!', err); }); }; }
Далее мы можем использовать следующую **конструкцию**, чтобы **обернуть** нашу функцию `loadCities()` в функцию `handleError()`
Plain Text const getCitiesWithErrorHandler = handleError(loadCities); getCitiesWithErrorHandler(); ```
Таким образом мы сделаем наш код более простым и менее громоздким.