Async и await
Внедение стандарта ES2017 в JavaScript привнесло два новых оператора: async
и await
, который призваны упростить работу с классическими промисами.
Синтаксис async/await
позволяет нам работать с асинхронным кодом так, как будто мы работаем с синхронным. Ключевое слово async
позволяет сделать из обычной функции, асинхронную функцию. Такая функция делает две вещи:
- Оборачивает возвращаемое значение в
Promise
- Позволяет использовать ключевое слово
await
Принцип работы
Оператор async
определяет асинхронную функцию, в которой будет выполняться одна или несколько асинхронных задач:
async function название_функции(){
// асинхронные операции
}
Внутри асинхронной функции мы можем применить оператор await
. Он ставится перед вызовом асинхронной операции, которая представляет объект Promise
:
async function название_функции(){
await асинхронная_операция();
}
Оператор await
приостанавливает выполнение асинхронной функции, пока асинхронная операция, объект Promise
не возвратит результат. Стоит учитывать, что оператор await
может использоваться только внутри функции, к которой применяется оператор async
.
Async
Асинхронные функции — это такие функции, которые объявлены с использованием ключевого слова async
, оператор 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();
// "Hello!"
result.then(value => console.log(value));
Или упрошенный вариант:
const hello = async() => {
return 'Hello!';
}
// "Hello!"
hello().then(value => console.log(value));
Когда в асинхронной функции возникает ошибка, то она завершается с отвергнутым обещанием. В этом случае для его обработки можно использовать метод catch()
:
const someFunc = async() => {
throw new Error('Oops');
}
// Error: Oops
someFunc().catch(error => console.error(error));
Внутри асинхронных функций перед промисами можно указывать await
.
Await
await
это ключевое слово, которое используется в асинхронных функциях для того, чтобы указать, что здесь необходимо дождаться завершения промиса. Таким образом await
, указанный перед промисом запрещает интерпретатору перейти к следующей строчке кода, пока он не выполнится. Выполним последовательно (один после завершения другого) три промиса внутри асинхронной функции:
// функция, возвращающая промис
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
не приводит к «зависанию» страницы и не мешает пользователю взаимодействовать с ней.
Обработка ошибок
Обработать потенциальные ошибки в асинхронной функции можно с помощью 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 {
// выполнится в любом случае, в независимости от того произошла ошибка или нет
}
}
Пример кода
Для примера переделаем наш код с использованием async/await
:
function math() {
return new Promise((resolve, reject) => {
// оценку, которые мы получим определим случайным образом спустя некоторое время (например, 5 секунд)
setTimeout(() => {
// сгенерируем оценку от 2 до 5
const mark = Math.floor(Math.random() * 4) + 2;
// если оценка больше 3, то...
if (mark > 3) {
// завершим промис успешно, для этого вызовем функцию resolve() и передадим в функцию оценку
resolve(`Ура! Я сдал экзамен на ${mark}! Папа, как и обещал дал мне 100$.`);
} else {
// завершим промис с ошибкой, для этого вызовем функцию reject() и передадим в функцию оценку
reject(`Увы, я получил оценку ${mark}! Папа мне не дал 100$`);
}
}, 5000);
});
}
async function message() {
try {
// успешное выполнение
console.log(await math());
} catch(e) {
// если что-то пойдёт не так в блоке try, мы автоматически попадём в метод catch()
console.error(e);
}
}
// вызываем асинхронную функцию
message()