Асинхронные функции 101
- пятница, 14 апреля 2017 г. в 03:14:39
Одним из основных преимуществ JavaScript является то, что всё асинхронно. По большей части различные части вашего кода не влияют на выполнение других.
doALongThing(() => console.log("I will be logged second!"));
console.log("I will be logged first!");
К сожалению, это также один из основных недостатков JavaScript. Задача выполнения синхронного кода становится сложнее, так как по умолчанию всё асинхронно.
Первым решением проблемы были колбэки. Если часть нашего кода зависит от какого-то результата, мы должны были вложить наш код —
doSomething((response) => {
doSomethingElse(response,(secondResponse) => {
doAThirdThing(secondResponse);
});
})
Вложенные колбэки в колбэках, как мы знаем, становятся непригодными. Таким образом, были созданы промисы (Promise). Они позволили нам работать с синхронными кодом более чистым и плоским способом.
doSomething()
.then((response) => doSomethingElse(response));
.then((secondResponse) => doAThirdThing(secondResponse));
// Even cleaner
doSomething().then(doSomethingElse).then(doAThirdThing);
Как и всё, промисы тоже не идеальны. Таким образом, в рамках спецификации ES2017 был определен другой метод для работы с синхронным кодом: Асинхронные функции. Это позволяет нам писать асинхронный код, как если бы он был синхронным.
Асинхронная функция определяется выражением асинхронной функции. Базовая функция выглядит так:
async function foo() {
const value = await somePromise();
return value;
}
Мы определяем функцию как асинхронную с помощью async
. Это ключевое слово может использоваться с любым синтаксисом объявления функции —
// Basic function
async function foo() { … }
// Arrow function
const foo = async () => { … }
// Class methods
class Bar {
async foo() { … }
}
Как только мы определили функцию как асинхронную, мы можем использовать ключевое слово await
.
Это ключевое слово помещается перед вызовом промиса, оно приостанавливает выполнение функции до тех пор, пока промис не будет выполнен или отклонён.
Обработка ошибок в асинхронных функциях выполняется с помощью блоков try
и catch
.
Первый блок (try) позволяет нам попробовать совершить действие. Второй блок (catch), вызывается, если действие не выполняется. Он принимает один параметр, содержащий любую ошибку.
async function foo() {
try {
const value = await somePromise();
return value;
}
catch (err) {
console.log("Oops, there was an error :(");
}
}
Асинхронные функции не являются заменой промисов. Они идентичны по своей природе. Так, асинхронная функция ожидает исполнения обещания и всегда возвращает промис.
Промис, возвращаемый асинхронной функцией, будет разрешен с любым значением, возвращаемым функцией.
async function foo() {
await somePromise();
return ‘success!’
}
foo().then((res) => console.log(res)) // ‘success!’
Если будет выброшена ошибка, промис будет отклонён с этой ошибкой.
async function foo() {
await somePromise();
throw Error(‘oops!’)
}
foo()
.then((res) => console.log(res))
.catch((err) => console.log(err)) // ‘oops!’
С промисами мы можем выполнять несколько обещаний параллельно с помощью метода Promise.all ().
function pause500ms() {
return new Promise((res) => setTimeout(res, 500));
}
const promise1 = pause500ms();
const promise2 = pause500ms();
Promise.all([promise1, promise2]).then(() => {
console.log("I will be logged after 500ms");
});
С асинхронными функциями нам нужно немного поработать, чтобы получить такой же эффект. Если мы просто перечислим каждую функцию, ожидающую в очереди, они будут выполняться последовательно, так как await
приостанавливает выполнение оставшейся части функции.
async function inSequence() {
await pause500ms();
await pause500ms();
console.log("I will be logged after 1000ms");
}
Это займет 1000 мс, так как второе ожидание не запустится, пока не завершится первое. Чтобы обойти это, мы должны ссылаться на функции таким образом:
async function inParallel() {
const await1 = await pause500ms();
const await2 = await pause500ms();
await await1;
await await2;
console.log("I will be logged after 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));
Происходит следующее:
На момент написания статьи, асинхронные функции и промисы доступны в текущих версиях всех основных браузеров, за исключением Internet Explorer и Opera Mini.