Написание асинхронного кода с помощью async/await

Написание асинхронного кода с помощью async/await
Содержание:
  1. Асинхронные функции
  2. Await
  3. Обработка ошибок
  4. Комментарии

В этой статье разберём как работает async/await и каким образом его использовать.

Async/await — это специальный синтаксис, который предназначен для более простого и удобного написания асинхронного кода. Появился он в языке, начиная с ES2017 (ES8).

Синтаксис «async/await» упрощает работу с промисами (позволяет асинхронный код записывать синхронным способом).

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

Асинхронные функции — это такие, которые объявлены с использованием ключевого слова async.

JavaScript
// асинхронная функция delay
async function delay() {
  // ...
}

// асинхронная функция как часть выражения
const wait = async function() {
  // ...
}

// стрелочная запись асинхронной функции sleep
const sleep = async() => {
  // ...
}

В качестве результата асинхронная функция всегда возвращает промис. Если в качестве значения вернуть не промис, то она автоматически обернёт его в успешно завершившийся промис.

JavaScript
const hello = async() => {
  return 'Hello!';
}

const result = hello();

console.log(result); // [object Promise] { ... }

Т.е. результат будет одинаковым, если вместо 'Hello!' вернуть Promise.resolve('Hello!'):

JavaScript
const hello = async() => {
  return Promise.resolve('Hello!');
}

Так как возвращаемое значение является промисом, то мы можем его обработать с помощью метода then():

JavaScript
const hello = async() => {
  return 'Hello!';
}

const result = hello();

result.then(value => console.log(value)); // "Hello!"

...или просто:

JavaScript
const hello = async() => {
  return 'Hello!';
}

hello().then(value => console.log(value)); // "Hello!"

Когда в асинхронной функции возникает ошибка, то она завершается с отвергнутым обещанием. В этом случае для его обработки можно использовать метод catch():

JavaScript
const someFunc = async() => {
  throw new Error('Oops');
}

someFunc().catch(error => console.error(error)); // Error: Oops

Внутри асинхронных функций перед промисами можно указывать await.

Await

await – это ключевое слово, которое используется в асинхронных функциях для того, чтобы указать, что здесь необходимо дождаться завершения промиса. Таким образом await, указанный перед промисом запрещает интерпретатору перейти к следующей строчке кода, пока он не выполнится.

Синтаксис:

JavaScript
// result – переменная, содержащая результат выполнения промиса
let result = await promise;
// на эту строчку не перейдём пока не завершится промис, приведённый выше

Например, выполним последовательно (один после завершения другого) три промиса внутри асинхронной функции:

JavaScript
// функция, возвращающая промис
function delay(ms, str) {
  return new Promise (resolve => setTimeout(() => {
    resolve(str);
  }, ms));
}
// асинхронная функция
async function message() {
  // ждём выполнение первого промиса delay(1000, 'Игорь') и сохраняем его результат в переменную a
  let a = await delay(1000, 'Игорь');
  // после завершения первого промиса, переходим к выполнению второго delay(2000, 'Егор')
  let b = await delay(2000, 'Егор'); // как только он завершится помещаем его результат в переменную b
  // после завершения второго промиса, переходим к выполнению третьего delay(4000, 'Денис')
  let c = await delay(4000, 'Денис'); // как только он выполнится сохраняем его результат в переменную c

  // выводим значения переменных в консоль
  console.log(`${a} ${b} ${c}`); // "Игорь Егор Денис"
}

// вызываем асинхронную функцию
message();

Не смотря на то, что await заставляет интерпретатор остановиться и дождаться завершения промиса, движок JavaScript в это время может выполнять другие задачи. Это достигается благодаря тому, что асинхронный код выполняется в фоне и не блокирует основной поток. Таким образом, await не приводит к «зависанию» страницы и не мешает пользователю взаимодействовать с ней.

По сути, «async/await» – это просто удобный способ работать с промисами. Если переписать код, приведённый выше через промисы (без использования «async/await»), то получится примерно следующее:

JavaScript
// функция, возвращающая промис
function delay(ms, str) {
  return new Promise (resolve => setTimeout(() => {
    resolve(str);
  }, ms));
}
// код, переписанный через промисы
let a, b, c;
delay(1000, 'Игорь')
  .then(value => {
    a = value;
    return delay(2000, 'Егор');
  })
  .then(value => {
    b = value;
    return delay(4000, 'Денис');
  })
  .then(value => {
    c = value;

    console.log(`${a} ${b} ${c}`); // "Игорь Егор Денис"
  })

Обработка ошибок

Обработать потенциальные ошибки в асинхронной функции можно с помощью try..catch. Для этого этот блок кода (в котором используется await) необходимо заключить в эту конструкцию.

Например:

JavaScript
async function getUser() {
  try {
    const response = await fetch(url);
    const data = await response.json();
  } catch(e) {
    // если что-то пойдёт не так на каком-то этапе в блоке try, то мы автоматически попадём в метод catch()
    console.error(e);
  }
}

Если нужно с finally, то так:

JavaScript
async function getUser() {
  try {
    const response = await fetch(url);
    const data = await response.json();
  } catch(e) {
    // если что-то пойдёт не так на каком-то этапе в блоке try, то мы автоматически попадём в метод catch()
    console.error(e);
  } finally {
    // выполнится в любом случае, в независимости от того произошла ошибка или нет
  }
}

Пример, в котором напишем асинхронный код для получения данных с сервера JSONPlaceholder:

Асинхронный код для получения данных с сервера JSONPlaceholder
JavaScript
// асинхронная функция для получения данных пользователя в формате JSON
const getUser = async(id) => {
  try {
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
    if (!response.ok) {
      throw new Error('Ответ сервера не в диапазоне 200-299');
    }
    const data = await response.json();

    return data;
  } catch (e) {
    throw new Error('Ошибка при получении данных пользователя');
  }
}
// функция для отображения данных пользователя на странице
const renderUsers = (users) => { ... }
// асинхронная функция, в которой сначала вызывается getUser(), а затем renderUsers() для отображения полученных на странице
const clickUser = async(id) => {
  try {
    const data = await getUser(id);
    const users = Array.isArray(data) ? data : [data];
    renderUsers(users);
  } catch (e) {
    console.error(e);
  }
}

Комментарии: 0