Async/await в языке программирования Javascript — это синтаксис для работы с промисами и асинхронностью в JS. Кроме того, синтаксический сахар для Promises API.
Как работает async/await в JavaScript: основы асинхронности
Программисты пишут код в синхронном стиле и предполагают, что строчки будут выполняться друг за другом (цепочка из then):
- Встать с утра.
- then.Поехать на работу.
- then.Открыть компьютер.
- then.Начать писать код.
- then.Пообедать с коллегами.
Но иногда так не работает. Например, чтобы начать писать code на проекте, нужны доступ к интернету, но сегодня у провайдера и проблемы и непонятно, когда они решатся. А коллега, от которого требуется задача, подключится через 3–5 часов.
Ждать и ничего не делать было бы странно. Поэтому, когда задача «писать код на проекте» зависает, программист решает другие вопросы: например, созванивается с коллегами по телефону или приводит в порядок рабочие файлы на ПК. В языке программирования JavaScript асинхронность работает почти так же, как в жизни.
Синтаксис async\await в JavaScript появился для того, чтобы упростить работу с промисами. Больше не нужно ждать, когда завершится один then, чтобы начал выполняться следующий.
let MyPromise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Ой, что-то пошло не так!")), 1000);
});
MyPromise.then(
result => alert(result),
error => alert(error)
);
MyPromise.catch(alert);
Преимущества async/await перед колбэками и промисами
Цепочку промисов можно сравнить с передачей эстафеты. Новый промис (бегун) не может стартовать, пока не выполнил свою задачу предыдущий. Async/await — это одновременный масс-старт бегунов: никто никого не ждет, каждый финиширует сам. Колбэк можно сравнить с бегом «с условием»: «Когда прибежишь на остановку и сядешь на автобус, позвони. От твоего звонка будет зависеть, ставить ли мне чайник (запускать ли функцию)».
Callbacks порождают пирамиду вызовов, которую часто называют «callback hell» («ад колбэков»).
При использовании then появляется цепочка «зенов», каждый из которых возвращает промис. При этом следующий then на стопе: он не будет выполняться, пока не выполнен предыдущий.
Почему асинхронное программирование важно для JavaScript
Синхронный код подразумевает получение результата, как только браузер может его вернуть. Но это возможно не всегда.
Например, нужно выполнить запрос к серверу и загрузить видео в 4К или фотографию в высоком разрешении.
Синхронный JavaScript не сработает (псевдокод ниже покажет ошибку):
const response = fetch("myImage.png");
let blob = response.blob();
JS попытается выполнить const response (запрос на сервер), а затем сразу же — let blob. Выпадает ошибка: видео или картинка грузится, response еще недоступен. Более того: сложно сказать, когда он будет доступен: загрузка может идти и 3 мс, и 30 секунд.
Асинхронный JavaSrcipt как раз и решает эту проблему. Он начнет выполнять const blob, когда будет точно знать, что в response что-то пришло.
Существуют два стиля асинхронного кода в JavaScript;
- колбэки (callbacks);
- промисы (promises).
Асинхронные операции помещаются в очередь событий. Она запускается после того, как завершена обработка основного потока: так можно гарантировать отсутствие блокировок JavaScript-кода.
Синтаксис и использование async и await: пошаговое руководство
Ниже пошагово разберем, как написать первую функцию на async/await.
Создание асинхронной функции с async
Зарезервированное в языке программирования слово async обязательно указывается перед функцией. Он означает, что f вернет промис. У него три состояния: pending (в процессе), done (resolve fulfilled) и ошибка (reject)
async function GitVerseFunction() {
return 'Hi, we are gitverse.ru';
}
GitVerseFunction().then(alert);
В примере выше GitVerseFunction() вернула выполненный промис. Результат, который пришел в return, — 'Hi, we are gitverse.ru'.
Использование await для упрощения работы с промисами
Термин переводится с английского как «ждать», и эвейт действительно заставляет интерпретатор JavaScript ждать. Нужно время, чтобы выполнился промис, указанный в коде после await. Затем возвращается его результат. Когда это сделано, продолжается выполнение кода.
Пример простой асинхронной функции, в которой используется async/await:
async function GitVerseFunction() {
const GitVersePromise = new Promise((resolve, reject) => {
setTimeout(() => resolve("Я все сделал!"), 2000)
});
const res = await GitVersePromise;
alert(res);
}
GitVerseFunction();
Выполнение function GitVerseFunction остановится на выделенной жирным строчке: ждем, пока выполнится промис. Поскольку срабатывает setTimeout(), это случится через 2 сек. после того, как запущена функция. Затем в переменную res записывается результат выполнения промиса. В браузере появляется предупреждение: «Я все сделал!».
Ожидание выполнения promise — не очень трудозатратная операция. Все почти как у людей: на время ожидания JS-движок может заняться другими задачами — выполнением скриптов или обработкой событий.
Примеры использования async/await в реальных сценариях
Естественно, вряд ли кто-то будет использовать нечто подобное, чтобы работать с setTimeout(). Главное предназначение — работа с API и загрузка данных из сети (хотя и не только оно).
Обработка запросов к API с использованием async/await
Например, нужно сделать запрос к api.github.com и достать оттуда локацию (location) нужного нам пользователя — sberdev. Код выглядит так:
async function getLocation() {
const url = 'https://api.github.com/users/sberdev';
try {
const response = await fetch(url);
const data = await response.json();
if (!response.ok) {
throw new Error(`Ошибка при получении данных: ${response.status}`);
}
const location = data.location;
console.log(location);
} catch (error) {
console.error('Ошибка:', error);
}
}
getLocation();
В реальном проекте разработчики убрали бы подсветку ESLint-а и использовали шаблонные строки в запросе (не https://api.github.com/users/sberdev, а `https://api.github.com/users/${username}`). Но пока мы осваиваем работу с async/await и видим результат в консоли.
В консоль выводится Russian Federation. Аналогично можно изменить код и получить другую информацию о sberdev, предоставляемую API: email, bio, type, blog.
AI-ассистент разработчика GIGA CODE может объяснить, что происходит в коде:
Параллельные и последовательные запросы в асинхронном коде
Последовательные запросы с .then выполняются по очереди: следующий начинает выполняться только после того, как завершатся предыдущие. Более того, результаты «предшественника» влияют на следующий шаг. Цепочка из then может получиться бесконечно длинной.
Параллельные запросы выполняются одновременно. Никто не ждет, пока завершится предыдущий, чтобы начал исполняться следующий.
Как обрабатывать ошибки в async/await: try/catch и лучшие практики
Когда promise завершается успешно, await promise возвращает результат. Если промис завершается ошибкой, будет выброшено исключение — внештатное резкое прерывание программы и выброс через все уровни до той точки, где будет try… catch.
Если в коде нет блока try … catch, асинхронная функция будет возвращать promise в состоянии rejected. Разработчик сможет использовать .catch промиса, чтобы обработать ее.
Для работы с ошибками можно:
- создавать собственные классы ошибок;
- использовать специфические обработчики и условия (if… else);
- логировать ошибки в консоль.
Частые ошибки при использовании async/await и их исправление
- Код по-прежнему остается асинхронным. Но не стоит делать цепочку из 5–10 эвейтов.
- Promise.catch() также возвращает промис (return promise).
- Эвейт нельзя использовать в синхронных функциях, а также вне функций.
Рассмотрим, почему зарезервированное слово await нельзя использовать в обычных функциях, написанных без async. При попытке сделать это падает ошибка: «Await is only valid in async functions and the top level bodies of modules».
function GitVerseFunction() {
let GitVersePromise = Promise.resolve(1);
const res = await GitVersePromise;
}
Рекомендации по применению async/await в асинхронном программировании
- Код может стать медленнее, если в нем будет много awaited promises.
- Проблемы с поддержкой браузерами помогает решить BabelJS.
- Fetch — нативная функция браузера. На проектах часто используют Axios — JavaScript-библиотека (HTTP-клиент на промисах), которая предоставляет большую функциональность.