Асинхронные функции

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));
Copy

Согласитесь, не самый легкочитаемый код, при том, что операции очень простые. Внутри then мы передаем функции-обработчики и в результате получаются елки вложенности. Вот бы можно было сделать так, что наш код выглядел следующим образом.

const promise = fetch('/users');
/*
 * Как-то заставить JS подождать пока выполнится промис
 * После чего просто использовать данные, без then и т. д.
 */
console.log(promise.result);
Copy

Но проблема в том, что 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));
Copy

При такой записи, 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));
Copy
  • Не забывайте ставить 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));
Copy

3. Дополнительные материалы

Last updated