Одним из основных преимуществ JavaScript является то, что все асинхронно. Чаще всего, различные части вашего кода не влияют на выполнение других.
doALongThing(() => console.log("Я попаду в лог вторым!"));
console.log("Я попаду в лог первым");
К сожалению, это также один из основных недостатков JavaScript. Поскольку по умолчанию все асинхронно, если вы захотите выполнить код синхронно это станет проблемой. Первым решением этой проблемы были callbacks (функции обратного вызова). Если часть вашего кода зависела от результата другого, нам бы пришлось сделать вложение -
doSomething((response) => {
doSomethingElse(
response,
(secondResponse) => {
doAThirdThing(secondResponse);
});
})
Вложенные обратные вызовы обратных вызовов, как известно, стали недостижимыми. Итак, создали еще решение обещания. Что позволило нам иметь дело с синхронным кодом в гораздо более чистом, плоском виде.
doSomething()
.then((response) => doSomethingElse(response))
.then((secondResponse) => doAThirdThing(secondResponse));
// Можно упростить до doSomething().then(doSomethingElse).then(doAThirdThing);
Как и со всем, обещания тоже не были идеальными. Таким образом, в рамках спецификации ES2017 был определен другой метод для работы с синхронным кодом; Асинхронные функции. Это позволяет нам писать асинхронный код, как если бы он был синхронным.
Создание асинхронных функций
Асинхронная функция определяется с помощью ключевого слова async. Базовая функция выглядит так:
async function foo() {
const value = await somePromise();
return value;
}
Мы определяем функцию как асинхронную, подставляя async объявлению функции. Это ключевое слово может использоваться с любым синтаксисом объявления функции -
// Обычная функция
async function foo() { … }
// Стрелочная функция
const foo = async () => { … }
// Метод класса
class Bar { async foo() { … } }
Как только мы определили функцию как асинхронную, мы можем использовать ключевое слово await. Это ключевое слово помещается перед вызовом обещания, которое приостанавливает выполнение функции до тех пор, пока обещание не будет выполнено или отклонено.
Обработка ошибок
Обработка ошибок в асинхронных функциях выполняется с помощью блоков try и catch. Первый блок, try, позволяет нам выполнить действие. Второй блок catch, вызывается, если действие не выполняется. Он принимает один параметр, содержащий любую ошибку.
async function foo() {
try {
const value = await somePromise();
return value;
} catch (err) {
console.log("Упс, сдесь произошла ошибка :(");
}
}
Использование асинхронных функций
Асинхронные функции не являются заменой обещаний. Они работают рука об руку. Асинхронная функция ожидает (await) исполнения обещания, и асинхронная функция всегда возвращает обещание.
Обещание, возвращаемое асинхронной функцией, будет разрешено с любым значением.
async function foo() {
await somePromise();
return 'Успех!'
}
foo().then((res) => console.log(res)) // Успех!
Если будет выдана ошибка, обещание будет отклонено с этой же ошибкой.
async function foo() {
await somePromise();
throw Error('Упс!')
}
foo()
.then((res) => console.log(res))
.catch((err) => console.log(err)) // Упс!
Выполнение асинхронных функций в параллельном режиме
Мы можем выполнить несколько обещаний параллельно с помощью метода Promise.all()
.
function pause500ms() {
return new Promise((res) => setTimeout(res, 500));
}
const promise1 = pause500ms();
const promise2 = pause500ms();
Promise.all([promise1, promise2]).then(() => {
console.log("Я попаду в лог через 500ms");
});
С асинхронными функциями нам нужно немного поработать, чтобы получить тот же эффект. Если мы просто перечислим каждую функцию, ожидающую в последовательности, они будут выполняться последовательно, так как await приостанавливает выполнение остальной функции.
async function inSequence() {
await pause500ms();
await pause500ms();
console.log("Я попаду в лог через 1000ms");
}
Это займет 1000 мс, так как второе await не запускается, пока не завершится первое. Чтобы обойти это, мы должны ссылаться на функции таким образом -
async function inParallel() {
const await1 = pause500ms();
const await2 = pause500ms();
await await1;
await await2;
console.log("Я попаду в лог через 500ms");
}
Это займет всего 500 мс, потому что обе функции pause500ms()
будут выполняться одновременно.
Обещания или асинхронные функции
Как я уже упоминал, асинхронные функции не заменяют обещаний, они используются вместе. Асинхронные функции предоставляют альтернативный, а в некоторых случаях и лучший способ работы с функциями основанными на обещаниях. Но они все еще используют и производят обещания.
Поскольку возвращается обещание, асинхронная функция может быть вызвана другой асинхронной функцией или обещанием. Мы можем смешивать и сопоставлять в зависимости от того, какой синтаксис лучше всего подходит для каждого случая.
function baz() {
return new Promise((res) => setTimeout(res, 1000));
}
async function foo() {
await baz();
return 'foo complete!';
}
async function bar() {
const value = await foo();
console.log(value);
return 'bar complete!';
}
bar().then((value) => console.log(value));
Произойдет следующее:
- Ожидание 1000мс
- Запись в лог "foo complete!"
- Запись в лог "bar complete!"
Поддержка
На момент написания, и асинхронные функции и обещания доступны в текущих версиях всех основных браузеров, за исключением Internet Explorer и Opera Mini.