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

В этой статье разберём как работает async/await и каким образом его использовать.
Async/await — это специальный синтаксис, который предназначен для более простого и удобного написания асинхронного кода. Появился он в языке, начиная с ES2017 (ES8).
Синтаксис «async/await» упрощает работу с промисами (позволяет асинхронный код записывать синхронным способом).
Асинхронные функции
Асинхронные функции — это такие, которые объявлены с использованием ключевого слова async
.
// асинхронная функция delay
async function delay() {
// ...
}
// асинхронная функция как часть выражения
const wait = async function() {
// ...
}
// стрелочная запись асинхронной функции sleep
const sleep = async() => {
// ...
}
В качестве результата асинхронная функция всегда возвращает промис. Если в качестве значения вернуть не промис, то она автоматически обернёт его в успешно завершившийся промис.
const hello = async() => {
return 'Hello!';
}
const result = hello();
console.log(result); // [object Promise] { ... }
Т.е. результат будет одинаковым, если вместо 'Hello!'
вернуть Promise.resolve('Hello!')
:
const hello = async() => {
return Promise.resolve('Hello!');
}
Так как возвращаемое значение является промисом, то мы можем его обработать с помощью метода then()
:
const hello = async() => {
return 'Hello!';
}
const result = hello();
result.then(value => console.log(value)); // "Hello!"
...или просто:
const hello = async() => {
return 'Hello!';
}
hello().then(value => console.log(value)); // "Hello!"
Когда в асинхронной функции возникает ошибка, то она завершается с отвергнутым обещанием. В этом случае для его обработки можно использовать метод catch()
:
const someFunc = async() => {
throw new Error('Oops');
}
someFunc().catch(error => console.error(error)); // Error: Oops
Внутри асинхронных функций перед промисами можно указывать await
.
Await
await
– это ключевое слово, которое используется в асинхронных функциях для того, чтобы указать, что здесь необходимо дождаться завершения промиса. Таким образом await
, указанный перед промисом запрещает интерпретатору перейти к следующей строчке кода, пока он не выполнится.
Синтаксис:
// result – переменная, содержащая результат выполнения промиса
let result = await promise;
// на эту строчку не перейдём пока не завершится промис, приведённый выше
Например, выполним последовательно (один после завершения другого) три промиса внутри асинхронной функции:
// функция, возвращающая промис
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»), то получится примерно следующее:
// функция, возвращающая промис
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
) необходимо заключить в эту конструкцию.
Например:
async function getUser() {
try {
const response = await fetch(url);
const data = await response.json();
} catch(e) {
// если что-то пойдёт не так на каком-то этапе в блоке try, то мы автоматически попадём в метод catch()
console.error(e);
}
}
Если нужно с finally
, то так:
async function getUser() {
try {
const response = await fetch(url);
const data = await response.json();
} catch(e) {
// если что-то пойдёт не так на каком-то этапе в блоке try, то мы автоматически попадём в метод catch()
console.error(e);
} finally {
// выполнится в любом случае, в независимости от того произошла ошибка или нет
}
}
Пример, в котором напишем асинхронный код для получения данных с сервера JSONPlaceholder:

// асинхронная функция для получения данных пользователя в формате 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);
}
}