JavaScript Async/Await за 10 минут
В течение долгого времени разработчикам JavaScript приходилось полагаться на колбэки для работы с асинхронным кодом. В результате многие из нас испытали на себе то, что принятно называть "callback hell", и ужас от функций вроде этой
К счастью, затем пришли промисы (Promise
). Они предложили гораздо более организованную альтернативу колбэкам, и большая часть JavaScript-сообщества быстро перешла к их использованию.
Теперь же, с приходом Async/Await, написание JavaScript кода станет еще удобнее!
Что такое Async/Await?
Async/Await - это долгожданная функция JavaScript, которая делает работу с асинхронными функциями более приятной и понятной. Он построен поверх Promises и совместим со всеми существующими API-интерфейсами на основе Promise
.
Название происходит от async
и await
- двух ключевых слов, которые помогут нам очистить наш
асинхронный код:
async
- объявляет асинхронную функцию (async function
someName(){...}
)
- Автоматически преобразует обычную функцию в Promise.
- Функции async резолвят (
resolve
) всё, что возвращается в их теле. - Асинхронные функции позволяют использовать
await
.
await
- приостанавливает выполнение функции (let result = await
someAsyncCall();
)
- Когда
await
помещен перед вызовом Promise,await
приостанавливает выполнение кода, следующего за ним, заставляя скрипт ждать возвращения результата выполняемого Promise'а. await
работает только с Promise'ами, он не работает с обычными колбэками.await
может использоваться только внутри асинхронных функций.
Вот простой пример, который, надеюсь, прояснит ситуацию:
Предположим, мы хотим получить некоторый JSON-файл с сервера. Мы напишем функцию, которая использует библиотеку axios
и отправляет HTTP-запрос GET для
https://tutorialzine.com/misc/files/example.json
Для его получения, мы должны ждать ответа от сервера, поэтому, естественно, HTTP-запрос будет асинхронным.
Ниже мы видим, что одна и та же функция реализована дважды. Сначала с Promise, затем второй раз с использованием Async/Await.
// Вариант с Promise
function getJSON(){
// Чтобы блокировать функцию, создаем Promise
return new Promise( function(resolve) {
axios.get('https://tutorialzine.com/misc/files/example.json')
.then( function(json) {
// Ответ от сервера доступен в блоке .then
// Возвращаем результат с помощью функции resolve
resolve(json);
});
});}
// Вариант Async/Await
// Ключевое слово async автоматически создаст новый Promise и возвратит его
async function getJSONAsync(){
// Ключевое слово await позволяет нам не писать блок .then
let json = await axios.get('https://tutorialzine.com/misc/files/example.json');
// Ответ на GET запрос сохранен в переменной json
// Возвращаем его как в обычной синхронной функции
return json;
}
Явно, что версия на Async/Await короче и читается легче. Помимо используемого синтаксиса обе функции идентичны - они обе возвращают Promise и резолвятся с JSON ответом от axios. Мы можем вызвать async функцию вот так:
getJSONAsync().then( function(result) {
// Сделать что-нибудь с результатом.
});
Итак, делает ли Async/Await устаревшими Promise'ы?
Нет. При работе с Async/Await мы все еще используем Promise под капотом. Хорошее понимание Promise'ов, на самом деле, очень рекомендуется и поможет вам в долгосрочной перспективе.
Есть даже случаи, когда Async/Await не сокращают код, и приходится возвращаться к Promise за помощью. Один из таких случаев - это когда нам нужно сделать несколько независимых асинхронных вызовов и дождаться их завершения.
Если мы попытаемся сделать это с помощью async и ожидаем, произойдет следующее:
async function getABC() {
let A = await getValueA(); // getValueA займет 2 секунды
let B = await getValueB(); // getValueB займет 4 секунды
let C = await getValueC(); // getValueC займет 3 секунды
return A*B*C;
}
Каждый вызов await
будет ждать, пока предыдущий не вернет результат. Поскольку мы выполняем один вызов
за раз, вся функция займет 9 секунд от начала до конца (2 + 4 + 3).
Это не оптимальное решение, так как три переменные A, B и C не зависят друг от друга. Другими словами, нам не нужно знать значение A до того, как мы получим B. Мы можем получить их в одно и то же время и уменьшить время ожидания.
Чтобы отправить все запросы одновременно, требуется Promise.all(). Это позволит убедиться, что у нас есть все результаты, прежде чем продолжить, но асинхронные вызовы будут запускаться параллельно, а не один за другим.
async function getABC() {
// Promise.all() позволяет выполнить все функции одновременно
let results = await Promise.all([ getValueA, getValueB, getValueC ]);
return results.reduce((total,value) => total * value);
}
Таким образом, функция займет гораздо меньше времени. Вызовы getValueA и getValueC будут уже завершены к концу getValueB. Таким образом мы сокращаем время исполнения скрипта до времени самого медленного (getValueB()), а не суммы всех взятых.
Обработка ошибок в Async/Await
Еще одна отличная вещь в Async/Await заключается в том, что она позволяет нам обнаруживать любые неожиданные ошибки в
старом блоке try/catch. Нам просто нужно обернуть наши await
следующим образом:
async function doSomethingAsync(){
try {
// Этот запрос может вызваться с ошибкой
let result = await someAsyncCall();
}
catch(error) {
// Если была ошибка - перехват ошибки будет тут
}
}
Блок catch будет обрабатывать ошибки, вызванные ожидаемыми (await
) асинхронными вызовами или любым
другим падающим с ошибкой кодом, который мы запишем внутри блока try.
Если нужно, мы также можем перехватить ошибки при выполнении функции async
. Поскольку все функции async
возвращают Promise'ы, мы можем просто включить обработчик события .catch()
при их вызове.
// Async функция без блока try/catch
async function doSomethingAsync(){
// Этот асинхронный запрос может вызвать ошибку
let result = await someAsyncCall();
return result;
}
// Перехватываем ошибку при вызове функции
doSomethingAsync().
.then(successHandler)
.catch(errorHandler);
Важно выбрать, какой метод обработки ошибок вы предпочитаете, и придерживаться его. Использование
try/catch
и .catch()
одновременно, скорее всего, приведет к проблемам.
Поддержка браузерами
Async/Await уже доступен в большинстве основных браузеров. Исключение составляет только IE11 - все остальные производители поймут ваш код на async/await без необходимости использования полифилов. Можно убедиться в этом по ссылке http://caniuse.com/#search=await
Если же такая совместимость вам не подходит, тогда всегда можно прибегнуть к транспайлерам вроде Babel или TypeScript.