async function

Оголошення async function визначає асинхронну функцію — функцію, яка є об'єктом AsyncFunction. Асинхронні функції мають окремий від решти функцій порядок виконання, через цикл подій, вертаючи неявний проміс в якості результату. Але синтаксис та структура коду, який використовує асинхронні функції, виглядають, як стандартні синхронні функції.

Ви також можете визначити асинхронну функцію за допомогою виразу async function.

Синтаксис

async function name([param[, param[, ... param]]]) {
   statements
}

Параметри

name
Ім'я функції.
param
Ім'я аргумента, що передається у функцію.
statements
Інструкції, що складають тіло функції.

Значення, що повертається

Об'єкт Promise, який буде вирішений зі значенням, поверненим асинхронною функцією, або відхилений з винятком, не перехопленим всередині асинхронної функції.

Опис

Асинхронна функція може містити вираз await, який призупиняє виконання функції, щоб дочекатись на вирішення об'єкта Promise, після чого відновлює виконання асинхронної функції та повертає вирішене значення.

Ключове слово await працює тільки всередині асинхронних функцій. Якщо ви використаєте його поза межами тіла асинхронної функції, то отримаєте помилку SyntaxError.

Поки асинхронна функція призупинена, функція, що її викликала, продовжує виконання (отримавши неявний проміс, повернений асинхронною функцією).

Метою async/await є спрощення синхронного використання промісів, а також виконання певних дій над групою промісів. Як проміси схожі на структуровані зворотні виклики, так використання async/await схоже на поєднання генераторів та промісів.

Приклади

Асинхронні функції та порядок виконання

function resolveAfter2Seconds() {
  console.log("починається повільний проміс")
  return new Promise(resolve => {
    setTimeout(function() {
      resolve("повільний")
      console.log("повільний проміс завершено")
    }, 2000);
  });
}

function resolveAfter1Second() {
  console.log("починається швидкий проміс")
  return new Promise(resolve => {
    setTimeout(function() {
      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()))()
  ])
}

// Ця функція не обробляє помилки. Дивіться застереження нижче!
function parallelPromise() {
  console.log('==ПАРАЛЕЛЬНИЙ з Promise.then==')
  resolveAfter2Seconds().then((message)=>console.log(message))
  resolveAfter1Second().then((message)=>console.log(message))
}

sequentialStart() // через 2 секунди виводить "повільний", далі через ще 1 секунду "швидкий"

// чекає, поки попередній завершиться
setTimeout(concurrentStart, 4000) // через 2 секунди виводить "повільний", а потім "швидкий"

// знову чекає
setTimeout(concurrentPromise, 7000) // такий самий, як і concurrentStart

// знову чекає
setTimeout(parallel, 10000) // справді паралельний: через 1 секунду виводить "швидкий", потім ще через 1 секунду "повільний"

// знову чекає
setTimeout(parallelPromise, 13000) // такий самий, як і parallel

await та паралелізм

У sequentialStart виконання відкладається на 2 секунди для першого await, а потім ще на секунду для другого await. Другий таймер не створюється, поки перший не завершиться, отже, код завершує виконання через 3 секунди.

У concurrentStart обидва таймери створюються і потім очікуються у await. Таймери виконуються конкурентно, це означає, що код завершує виконання через 2, а не через 3 секунди, тобто, як найповільніший таймер.
Однак, виклики await все одно запускаються один за одним, це означає, що другий await чекатиме, поки перший не завершиться. У цьому випадку результат швидшого таймера обробляється після повільнішого.

Якщо ви бажаєте виконувати дві або більше робіт паралельно, ви маєте використовувати await Promise.all([job1(), job2()]), як це показано у прикладі parallel.

async/await проти Promise#then та обробка помилок

Більшість асинхронних функцій також можна написати як звичайні функції, що використовують проміси. Однак, асинхронні функції менш каверзні, коли доходить до обробки помилок.

І concurrentStart, і concurrentPromise функціонально еквівалентні:

  • У concurrentStart, якщо будь-який з викликів з await-ом зазнає невдачі, виняток буде автоматично перехоплений, виконання асинхронної функції перерветься, а помилка спливе наверх через неявний проміс, що повертається.
  • Щоб те саме сталось у випадку з Promise, функція має потурбуватись щодо повернення об'єкту Promise, який захопить завершення функції. У concurrentPromise це означає повернення проміса з Promise.all([]).then() через return. До речі, попередня версія цього прикладу забула це зробити!

Однак, асинхронні функції все ж можуть ковтати помилки.
Візьміть для прикладу асинхронну функцію parallel. Якби вона не мала await (чи return) для повернення результату виклику Promise.all([]), будь-яка помилка не спливала б.
В той час, як приклад parallelPromise виглядає простішим, він взагалі не обробляє помилки! Для цього знадобилося б схожа конструкція return Promise.all([]).

Заміна ланцюжка промісів на асинхронну функцію

API, який вертає Promise, створить ланцюжок промісів, і це розбиває функцію на багато частин. Розглянемо наступний код:

function getProcessedData(url) {
  return downloadData(url) // вертає проміс
    .catch(e => {
      return downloadFallbackData(url) // вертає проміс
    })
    .then(v => {
      return processDataInWorker(v) // вертає проміс
    })
}

його можна переписати єдиною асинхронною функцією наступним чином:

async function getProcessedData(url) {
  let v
  try {
    v = await downloadData(url) 
  } catch(e) {
    v = await downloadFallbackData(url)
  }
  return processDataInWorker(v)
}

У наведеному вище прикладі немає оператора await після ключового слова return, тому що повернене значення async function неявно загортається у Promise.resolve.

return await promiseValue; проти return promiseValue;

Неявне загортання повернених значень у Promise.resolve не означає, що return await promiseValue; є функціонально еквівалентним return promiseValue;

Розглянемо наступну переробку наведеного вище коду, яка вертає null, якщо processDataInWorker відхиляється з помилкою:

async function getProcessedData(url) {
  let v;
  try {
    v = await downloadData(url)
  } catch(e) {
    v = await downloadFallbackData(url)
  }
  try {
    return await processDataInWorker(v) // Зауважте `return await` у порівнянні з `return`
  } catch (e) {
    return null
  }
}

Варіант return processDataInWorker(v); спричинив би відхилення об'єкта Promise, поверненого функцією, замість вирішення його зі значенням null, якщо processDataInWorker(v) відхилено.

Це висвітлює тонку різницю між return foo; та return await foo;return foo; негайно вертає foo і ніколи не викидає помилку, навіть якщо foo є промісом, який відхиляється. return await foo; чекатиме на вирішення чи відхилення foo, якщо це проміс, і викидає помилку до повернення, якщо його відхилено.

Специфікації

Специфікація Статус Коментар
ECMAScript (ECMA-262)
The definition of 'async function' in that specification.
Living Standard Початкове визначення у ES2017.
ECMAScript 2017 (ECMA-262)
The definition of 'async function' in that specification.
Standard

Сумісність з веб-переглядачами

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
async functionChrome Full support 55Edge Full support 15Firefox Full support 52IE No support NoOpera Full support 42Safari Full support 10.1WebView Android Full support 55Chrome Android Full support 55Firefox Android Full support 52Opera Android Full support 42Safari iOS Full support 10.3Samsung Internet Android Full support 6.0nodejs Full support 7.6.0
Full support 7.6.0
Full support 7.0.0
Disabled
Disabled From version 7.0.0: this feature is behind the --harmony runtime flag.

Legend

Full support  
Full support
No support  
No support
User must explicitly enable this feature.
User must explicitly enable this feature.

Див. також