Site icon 8HOST.COM

Функции async/await в JavaScript

Промисы в JavaScript – это механизм для простой и последовательной обработки асинхронности в коде. Учитывая, что человеческий мозг не предназначен для эффективной работы с асинхронностью, промисы стали важным и очень долгожданным дополнением. Функции async/await, дополнение к ES2017 (ES8), помогают нам писать синхронный внешне код, в котором все асинхронные задачи обрабатываются за кулисами.

Функциональность, которую вы можете получить с помощью async, можно воссоздать путем объединения промисов с генераторами. Но функция async дают разработчикам все, что нужно, избегая при этом лишнего кода.

Читайте также: Промисы в JavaScript ES6/ES2015

Простой пример асинхронной функции

В следующем примере мы объявляем функцию, которая возвращает промис, преобразующийся в значение 🤡 через 2 секунды. Затем мы объявляем асинхронную функцию и ждем разрешения промиса перед выводом сообщения в консоль:

function scaryClown() {
return new Promise(resolve => {
setTimeout(() => {
resolve('🤡');
}, 2000);
});
}
async function msg() {
const msg = await scaryClown();
console.log('Message:', msg);
}
msg(); // Message: 🤡 <-- after 2 seconds

Примечание: await – это новый оператор, предназначенный для ожидания разрешения или отклонения промиса. Его можно использовать только внутри функции async.

Преимущество функций async становится более очевидным, когда в коде задействовано несколько шагов:

function who() {
return new Promise(resolve => {
setTimeout(() => {
resolve('🤡');
}, 200);
});
}
function what() {
return new Promise(resolve => {
setTimeout(() => {
resolve('lurks');
}, 300);
});
}
function where() {
return new Promise(resolve => {
setTimeout(() => {
resolve('in the shadows');
}, 500);
});
}
async function msg() {
const a = await who();
const b = await what();
const c = await where();
console.log(`${ a } ${ b } ${ c }`);
}
msg(); // 🤡 lurks in the shadows <-- after 1 second

Однако нужно иметь в виду следующее: в приведенном выше примере каждый шаг выполняется последовательно, при этом каждый следующий шаг ожидает разрешения или отклонения предыдущего, и только потом включается в работу. Если вы хотите, чтобы все шаги выполнялись параллельно, вы можете просто использовать Promise.all, чтобы дождаться выполнения всех промисов:

// ...
async function msg() {
const [a, b, c] = await Promise.all([who(), what(), where()]);
console.log(`${ a } ${ b } ${ c }`);
}
msg(); // 🤡 lurks in the shadows <-- after 500ms

Promise.all возвращает массив с разрешенными значениями после разрешения всех переданных промисов.

Чтобы сделать код в приведенном выше примере лаконичным, мы использовали деструктуризацию массива.

Возвращение промисов

Асинхронные функции всегда возвращают промис, поэтому следующий код может не вывести ожидаемого результата:

async function hello() {
return 'Hello Alligator!';
}
const b = hello();
console.log(b); // [object Promise] { ... }

Поскольку возвращаемый результат является промисом, вы можете сделать следующее:

async function hello() {
return 'Hello Alligator!';
}
const b = hello();
b.then(x => console.log(x)); // Hello Alligator!

Или же:

async function hello() {
return 'Hello Alligator!';
}
hello().then(x => console.log(x)); // Hello Alligator!

Формы асинхронных функций

До сих пор в примерах мы демонстрировали async как объявление функции, но вы также можете определить выражения асинхронной функции и стрелочные функции async.

Выражения асинхронной функции

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

const msg = async function() {
const msg = await scaryClown();
console.log('Message:', msg);
}

Асинхронная стрелочная функция

Вот тот же пример, но на этот раз заданный как стрелочная функция:

const msg = async () => {
const msg = await scaryClown();
console.log('Message:', msg);
}

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

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

function yayOrNay() {
return new Promise((resolve, reject) => {
const val = Math.round(Math.random() * 1); // 0 or 1, at random
val ? resolve('Lucky!!') : reject('Nope 😠');
});
}
async function msg() {
try {
const msg = await yayOrNay();
console.log(msg);
} catch(err) {
console.log(err);
}
}
msg(); // Lucky!!
msg(); // Lucky!!
msg(); // Lucky!!
msg(); // Nope 😠
msg(); // Lucky!!
msg(); // Nope 😠
msg(); // Nope 😠
msg(); // Nope 😠
msg(); // Nope 😠
msg(); // Lucky!!

Учитывая, что асинхронные функции всегда возвращают промис, вы также можете работать с необработанными ошибками, используя оператор catch:

async function msg() {
const msg = await yayOrNay();
console.log(msg);
}
msg().catch(x => console.log(x));

Синхронная обработка ошибок работает не только при отклонении промиса, но и при синтаксической ошибке или фактической ошибке в среде выполнения. В следующем примере при втором вызове функции msg мы передаем числовое значение, которое не поддерживает метода toUpperCase в цепочке прототипов. Блок try…catch уловит и эту ошибку.

function caserUpper(val) {
return new Promise((resolve, reject) => {
resolve(val.toUpperCase());
});
}
async function msg(x) {
try {
const msg = await caserUpper(x);
console.log(msg);
} catch(err) {
console.log('Ohh no:', err.message);
}
}
msg('Hello'); // HELLO
msg(34); // Ohh no: val.toUpperCase is not a function

Асинхронные функции и API на основе промисов

Веб-API, основанные на промисах, являются идеальным вариантом для асинхронных функций:

async function fetchUsers(endpoint) {
const res = await fetch(endpoint);
let data = await res.json();
data = data.map(user => user.username);
console.log(data);
}
fetchUsers('https://jsonplaceholder.typicode.com/users');
// ["Bret", "Antonette", "Samantha", "Karianne", "Kamren", "Leopoldo_Corkery", "Elwyn.Skiles", "Maxime_Nienow", "Delphine", "Moriah.Stanton"]

Примечание: По состоянию на 2020 год 94% браузеров по всему миру могут обрабатывать async/await в javascript. Исключениями являются IE11 и Opera Mini.

Заключение

До функций async/await код JavaScript, полагающийся на множество асинхронных событий (например, код, который выполнял много вызовов API), заканчивал в так называемом «аду обратных вызовов» – так называется сверхсложная цепочка функций и обратных вызовов, которую очень трудно прочитать и понять.

Async и await позволяют писать асинхронный код JavaScript, который читается гораздо легче.