1. Введение
Работа с бекендом может быть довольно сложной, после одной асинхронной операции необходимо сделать еще один запрос на сервер по полученным данным и так несколько раз.
Представьте следующую ситуацию:
- Нам необходимо запросить профиль пользователя от
/user-profile - После того как мы получили профиль, нам необходимо запросить всех друзей этого
пользователя от
/users/:userId/friends - Мы можем запрашивать данные только одного из друзей за одно обращение к
серверу, допустим
/users/:userId - И только после того как мы запросили всех друзей, мы можем отобразить это пользователю.
Реализация такого алгоритма псевдокодом на промисах может выглядеть следующим образом:
fetch('/user-profile')
.then(user => fetch(`/users/${user.id}/friends`))
.then(idList => {
const friends = idList.map(id => fetch(`/users/${id}`));
return Promise.all(friends);
})
.then(friends => console.log(friends))
.catch(error => console.error(error));
Согласитесь, не самый легкочитаемый код, при том, что операции очень простые.
Внутри then мы передаем функции-обработчики и в результате получаются елки
вложенности. Вот бы можно было сделать так, что наш код выглядел следующим
образом.
const promise = fetch('/users');
/*
* Как-то заставить JS подождать пока выполнится промис
* После чего просто использовать данные, без then и т. д.
*/
console.log(promise.result);
Но проблема в том, что JavaScript не многопоточный язык, поэтому любое ожидание
в коде блокирует весь скрипт. Например когда мы используем alert и все виснет
до момента выхода из него. На помощь приходят генераторы.
2. Асинхронные функции
Асинхронные функции (async/await) - прослойка над промисами и генераторами, которая абстрагирует сложности их использования и предоставляет удобный интерфейс.
Для создания асинхронной функции перед function добавляем ключевое слово
async. После чего, внутри такой функции мы можем использовать оператор await
и справа от await поставить что-то, что вернет промис. Всегда в таком порядке
await -> promise.
const getUsers = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const users = response.json();
return users;
};
getUsers().then(users => console.log(users));
При такой записи, await приостановит только эту функцию (не весь скрипт) и
будет ждать, пока не выполнится промис. Как только промис выполнился -
исполнение функции возобновляется и на строке ниже нам доступен результат
асинхронной операции.
- Асинхронная функция всегда возвращает промис. Даже если написать
return 'hello', строка будет обернута в промис, который резолвится с этой строкой. - Оператор
awaitприостанавливает функцию до того момента, когда промис выполнился, то есть перешел в состояниеSettled - Оператор
awaitили вернет значение успешного(Fullfilled) промиса, или выбросит ошибку в случае если промис был отклонен(Rejected) - Оператор
awaitможно использовать только в теле асинхронной функции. - Можно использовать любые методы промисов внутри асинхронной функции, к примеру
Promise.all
Для того чтобы обработать ошибку используется конструкция try/catch- аналог
блока catch.
const getUsers = async () => {
try {
const result = await fetch('https://jsonplaceholder.typicode.com/users');
console.log(result);
} catch (err) {
throw err;
}
};
getUsers()
.then(users => console.log(users))
.catch(error => console.log(error));
- Не забывайте ставить
awaitперед промисом - Не используйте
awaitв методах вродеmap,filterи т. д., работает не так как можно ожидать - Хотя код выглядит синхронным, не забывайте, что исполнение кода в такой функции приостанавливается и возобновляется с течением времени.
Теперь перепишем наш код, используя асинхронную функцию.
const getUserFriends = async () => {
const user = await fetch('/user-profile');
const idList = await fetch(`/users/${user.id}/friends`);
const promises = idList.map(id => fetch(`/users/${id}`));
const friends = await Promise.all(promises);
return friends;
};
// Асинхронная функция всегда вернет промис
const promise = getUserFriends();
promise.then(friends => console.log(friends));