async function
Оголошення async function
створює зв'язування нової асинхронної функції з її новою назвою. Всередині її тіла дозволене вживання ключового слова await
, що дає змогу записувати асинхронну (засновану на промісах) поведінку в чистішому стилі та уникати необхідності явно задавати ланцюжки промісів.
Асинхронні функції також можна оголошувати за допомогою виразів async function
.
Спробуйте його в дії
Синтаксис
async function name(param0) {
statements
}
async function name(param0, param1) {
statements
}
async function name(param0, param1, /* …, */ paramN) {
statements
}
Примітка: Між
async
іfunction
не може бути символів кінця рядка, бо інакше буде автоматично вставлено крапку з комою, що зробитьasync
ідентифікатором, а все решту — оголошеннямfunction
.
Параметри
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("== sequentialStart починається ==");
// 1. Запустити таймер, вивести у консоль, коли він добіжить кінця
const slow = resolveAfter2Seconds();
console.log(await slow);
// 2. Запустити наступний таймер після очікування на попередній
const fast = resolveAfter1Second();
console.log(await fast);
console.log("== sequentialStart завершено ==");
}
async function sequentialWait() {
console.log("== sequentialWait починається ==");
// 1. Запустити два таймери, не чекаючи на жодний з них
const slow = resolveAfter2Seconds();
const fast = resolveAfter1Second();
// 2. Чекати завершення повільного таймера, а тоді вивести результат
console.log(await slow);
// 3. Чекати завершення швидкого таймера, а тоді вивести результат
console.log(await fast);
console.log("== sequentialWait завершено ==");
}
async function concurrent1() {
console.log("== concurrent1 починається ==");
// 1. Одночасно запустити два таймери, і чекати завершення обох
const results = await Promise.all([
resolveAfter2Seconds(),
resolveAfter1Second(),
]);
// 2. Вивести результати разом
console.log(results[0]);
console.log(results[1]);
console.log("== concurrent1 завершено ==");
}
async function concurrent2() {
console.log("== concurrent2 починається ==");
// 1. Запустити два таймери одночасно, а вивести зразу після завершення кожного з них
await Promise.all([
(async () => console.log(await resolveAfter2Seconds()))(),
(async () => console.log(await resolveAfter1Second()))(),
]);
console.log("== concurrent2 завершено ==");
}
sequentialStart(); // за 2 секунди виводить "повільно", а ще через 1 – "швидко"
// дочекатися завершення вище
setTimeout(sequentialWait, 4000); // за 2 секунди виводить "повільно", а тоді "швидко"
// чекати знову
setTimeout(concurrent1, 7000); // те саме, що й sequentialWait
// чекати знову
setTimeout(concurrent2, 10000); // за 1 секунду виводить "швидко", а через ще 1 – "повільно"
Вираз await та одночасність
У випадку послідовного старту у функції sequentialStart
виконання зупиняється на 2 секунди на першому await
, і потім іще на секунду на другому await
. Другий таймер не створюється, поки не сплине час першого, тому загалом код завершує роботу за 3 секунди.
У випадку одночасного старту у функції sequentialWait
обидва таймери створюються, а потім очікуються виразами await
. Таймери виконують відлік одночасно, тобто код завершить роботу радше за 2, ніж за 3 секунди. Інакше кажучи — тоді, коли завершить відлік найповільніший таймер. Втім, виклики await
все-таки виконуються послідовно, тобто другий await
чекатиме на завершення першого. В цьому випадку результат швидшого таймера обробляється після завершення роботи повільнішого.
Якщо потрібно безпечним чином виконати інші задачі після того, як дві або більше задач запущені одночасно та завершені, необхідно застосувати await
до виклику Promise.all()
або Promise.allSettled()
.
Застереження: Функції
sequentialWait
таconcurrent1
не є функціонально еквівалентними.Якщо у функції
sequentialWait
швидкий
проміс відхиляється перед сповненнямповільного
промісу, то буде викинуто помилку необробленого відхилення промісу, незалежно від наявності умовиcatch
у місці виклику.У функції
concurrent1
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 Ні | 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
- Вираз
async function
function
function*
async function*
await
Promise
- Декорування асинхронних функцій у JavaScript на innolitics.com (2016)