async function
Оголошення async function
оголошує асинхронну функцію, всередині тіла якої дозволене вживання ключового слова await
. Ключові слова async
та await
дають змогу записувати асинхронну (засновану на промісах) поведінку в чистішому стилі, уникаючи необхідності явно задавати ланцюжки промісів.
Асинхронні функції також можна оголошувати як вирази.
Спробуйте його в дії
Синтаксис
async function name(param0) {
statements
}
async function name(param0, param1) {
statements
}
async function name(param0, param1, /* … ,*/ paramN) {
statements
}
Параметри
name
Назва функції.
param
Необов'язковеНазва аргументу, який передається до функції.
statements
Необов'язковеІнструкції, що формують тіло функції. Може застосовуватись функціонал
await
.
Повернене значення
Проміс
, який буде вирішено зі значенням, поверненим з асинхронної функції, або відхилено з винятком, викинутим із (або не обробленим всередині) асинхронної функції.
Опис
Асинхронні функції можуть містити нуль чи більше виразів await
. Вираз await
змушує функції, які повертають проміси, поводити себе так, наче вони синхронні, шляхом призупинення виконання до моменту, коли повернений проміс буде сповнено або відхилено. Значення, повернене з виконаного промісу, обробляється як значення, повернене виразом await
. Вживання async
та await
дає змогу застосовувати звичайні блоки try
/ catch
навколо асинхронного коду.
Примітка: Ключове слово
await
дійсне лише всередині асинхронної функції в коді на JavaScript. Якщо його вжити за межами тіла асинхронної функції, програма викинеSyntaxError
.Самостійний
await
можна використовувати всередині модулів JavaScript.
Примітка: Мета
async
/await
— спростити синтаксис, необхідний для користування API, заснованим на промісах. Поведінкаasync
/await
— схожа до комбінації генераторів із промісами.
Асинхронні функції завжди повертають проміс. Якщо повернене з асинхронної функції значення не є явним промісом, воно буде неявно обгорнуте в нього.
Для прикладу, зверніть увагу на наступний код:
async function foo() {
return 1;
}
Він є аналогічним до такого фрагмента:
function foo() {
return Promise.resolve(1);
}
Примітка:
Попри те, що значення, повернене з асинхронної функції поводить себе так, наче воно загорнене у
Promise.resolve
, вони не еквівалентні.Асинхронна функція поверне нове посилання, тоді як
Promise.resolve
повертає те саме посилання, якщо переданим значенням є проміс.Це може спричинити проблеми, якщо необхідно перевірити рівність промісу і поверненого з асинхронної функції значення.
const p = new Promise((res, rej) => { res(1); }); async function asyncReturn() { return p; } function basicReturn() { return Promise.resolve(p); } console.log(p === basicReturn()); // true console.log(p === asyncReturn()); // false
Можна сприймати тіло асинхронної функції так, як наче воно поділене на частини виразами await
. Код верхнього рівня, аж до першого виразу await
включно (якщо такий є), — виконується синхронно. В такому разі асинхронна функція без виразу await
вся виконається синхронно. Проте якщо всередині тіла функції є принаймні один вираз await
, асинхронна функція завжди виконуватиметься асинхронно.
Наприклад:
async function foo() {
await 1;
}
Такий вираз також еквівалентний щодо наступного фрагмента:
function foo() {
return Promise.resolve(1).then(() => undefined);
}
Код після кожного виразу await
можна сприймати так, як наче він існує всередині функції зворотного виклику .then
. Таким чином, ланцюжок промісів поступово формується з кожним повторним входом у функцію. Повернене значення стає кінцевою ланкою ланцюжка.
В наступному прикладі відбувається очікування на виконання двох промісів. Процес виконання функції foo
відбувається у три етапи.
- Перший рядок тіла функції
foo
виконується асинхронно, причому виразawait
стає зайнятим очікуванням на виконання промісу. Виконання функціїfoo
на цьому зупиняється, і керування передається назад у функцію, яка викликалаfoo
. - Через деякий час, коли перший проміс сповнюється чи відхиляється, керування передається назад у
foo
. Результат виконання першого промісу (за умови, що проміс не було відхилено) повертається з виразуawait
. В цьому випадку1
присвоюється зміннійresult1
. Виконання функції продовжується, і обчислюється другий виразawait
. І знову, процес виконання функціїfoo
зупиняється, і керування передається назовні. - Через деякий час, коли вже другий проміс або сповнено, або відхилено, керування повертається до функції
foo
. Результат виконання другого промісу повертається з другого виразуawait
. Тут зміннійresult2
присвоюється значення2
. Керування передається виразуreturn
(якщо такий є). В цьому конкретному випадку повертається усталене значенняundefined
як вирішене значення промісу.
async function foo() {
const result1 = await new Promise((resolve) =>
setTimeout(() => resolve("1"))
);
const result2 = await new Promise((resolve) =>
setTimeout(() => resolve("2"))
);
}
foo();
Слід зауважити, що ланцюжок промісів будується не одразу. Натомість він конструюється етапами, по ходу успішного повернення керування з асинхронної функції – і передачі його назад до неї. Як наслідок, слід бути обережним з плином обробки помилок під час роботи з одночасними асинхронними операціями.
Наприклад, в наведеному нижче коді викинеться помилка неопрацьованого відхилення промісу, навіть якщо далі по ходу виконання ланцюжка промісів було встановлено обробник .catch
. Це відбувається через те, що p2
не буде "підключено до" ланцюжка промісів доти, доки керування не повернеться із p1
.
async function foo() {
const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
const p2 = new Promise((_, reject) => setTimeout(() => reject("2"), 500));
const results = [await p1, await p2]; // Так робити не слід! Натомість краще вжити Promise.all чи Promise.allSettled.
}
foo().catch(() => {}); // Спроба проковтнути всі помилки...
Оголошення async function
піднімаються нагору своєї області видимості й можуть викликатися на всьому її протязі.
Приклади
Асинхронні функції та порядок виконання
function resolveAfter2Seconds() {
console.log("початок повільного промісу");
return new Promise((resolve) => {
setTimeout(() => {
resolve("повільно");
console.log("повільний проміс виконано");
}, 2000);
});
}
function resolveAfter1Second() {
console.log("початок швидкого промісу");
return new Promise((resolve) => {
setTimeout(() => {
resolve("шкидко");
console.log("швидкий проміс виконано");
}, 1000);
});
}
async function sequentialStart() {
console.log("==ПОСЛІДОВНИЙ ПОЧАТОК==");
// 1. Виконання підходить до цього місця майже миттєво
const slow = await resolveAfter2Seconds();
console.log(slow); // 2. цей рядок виконується через 2 секунди після 1.
const fast = await resolveAfter1Second();
console.log(fast); // 3. цей рядок виконується через 3 секунди після 1.
}
async function concurrentStart() {
console.log('==ОДНОЧАСНИЙ ПОЧАТОК із "await"==');
const slow = resolveAfter2Seconds(); // негайно запускає таймер
const fast = resolveAfter1Second(); // негайно запускає таймер
// 1. Виконання підходить до цього місця майже миттєво
console.log(await slow); // 2. цей рядок виконується через 2 секунди після 1.
console.log(await fast); // 3. цей рядок виконується через 2 секунди після 1., негайно за 2., оскільки швидкий проміс уже виконано
}
function concurrentPromise() {
console.log('==ОДНОЧАСНИЙ ПОЧАТОК із "Promise.all"==');
return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then(
(messages) => {
console.log(messages[0]); // повільно
console.log(messages[1]); // швидко
}
);
}
async function parallel() {
console.log('==ПАРАЛЕЛЬНЕ виконання з "await Promise.all"==');
// Запускає 2 "завдання" паралельно, і очікує на завершення обох
await Promise.all([
(async () => console.log(await resolveAfter2Seconds()))(),
(async () => console.log(await resolveAfter1Second()))(),
]);
}
sequentialStart(); // через 2 секунди друкує "повільно", потім іще за 1 секунду — "швидко"
// очікує на завершення коду вище
setTimeout(concurrentStart, 4000); // через 2 секунди друкує "повільно", і потім "швидко"
// знову очікує
setTimeout(concurrentPromise, 7000); // так само, як і concurrentStart
// і знову очікує
setTimeout(parallel, 10000); // справді паралельно: через 1 секунду друкує "швидко", а потім іще за одну секунду — "повільно"
Вираз await і паралелізм
У випадку послідовного старту у функції sequentialStart
виконання зупиняється на 2 секунди на першому await
, і потім іще на секунду на другому await
. Другий таймер не створюється, поки не сплине час першого, тому загалом код завершує роботу за 3 секунди.
У випадку одночасного старту у функції concurrentStart
обидва таймери створюються, а потім очікуються виразами await
. Таймери виконують відлік одночасно, тобто код завершить роботу радше за 2, ніж за 3 секунди. Інакше кажучи — тоді, коли завершить відлік найповільніший таймер. Втім, виклики await
все-таки виконуються послідовно, тобто другий await
чекатиме на завершення першого. В цьому випадку результат швидшого таймера обробляється після завершення роботи повільнішого.
Якщо потрібно безпечним чином виконати паралельно два чи більше завдання, слід застосувати await
до виклику Promise.all
чи Promise.allSettled
.
Застереження: Функції
concurrentStart
таconcurrentPromise
не є функціонально еквівалентними.Якщо у функції
concurrentStart
швидкий
проміс відхиляється перед сповненнямповільного
промісу, то буде викинуто помилку необробленого відхилення промісу, незалежно від наявності умовиcatch
у місці виклику.У функції
concurrentPromise
Promise.all
зв'язує ланцюжок промісів за один прохід, тобто операція негайно завершить роботу в разі помилки незалежно від послідовності відхилення промісів, і помилка завжди відбуватиметься всередині налаштованого ланцюжка промісів, що дає змогу її обробити у звичайний спосіб.
Переписування ланцюжка промісів за допомогою асинхронної функції
API, який повертає проміс
, буде в результаті утворювати ланцюжки промісів, і це розбиває функцію на декілька частин. Якщо розглянути наступний код:
function getProcessedData(url) {
return downloadData(url) // повертає проміс
.catch((e) => downloadFallbackData(url)) // повертає проміс
.then((v) => processDataInWorker(v)); // повертає проміс
}
Це можна переписати як одну асинхронну функцію, як це показано нижче:
async function getProcessedData(url) {
let v;
try {
v = await downloadData(url);
} catch (e) {
v = await downloadFallbackData(url);
}
return processDataInWorker(v);
}
Крім того, можна з'єднувати в ланцюжок проміси з catch()
:
async function getProcessedData(url) {
const v = await downloadData(url).catch((e) => downloadFallbackData(url));
return processDataInWorker(v);
}
Зауважте, що в обох версіях переписаної функції немає інструкції await
після ключового слова return
, хоча це теж було б вірно. Повернене з асинхронної функції значення неявно обгортається у Promise.resolve
— якщо саме значення не є промісом (як це є в прикладах).
Специфікації
Сумісність із браузерами
desktop | mobile | server | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
async function statement
|
Chrome Full support 55 | Edge Full support 15 | Firefox Full support 52 | Internet Explorer No support No | Opera Full support 42 | Safari Full support 10.1 | WebView Android Full support 55 | Chrome Android Full support 55 | Firefox for Android Full support 52 | Opera Android Full support 42 | Safari on iOS Full support 10.3 | Samsung Internet Full support 6.0 | Deno Full support 1.0 | Node.js Full support 7.6.0 |
Дивіться також
- Вираз асинхронної функції
- Об'єкт
AsyncFunction
await
- Декорування асинхронних функцій у JavaScript (англ.) на innolitics.com