Await
У програмуванні, await — особливість, яка була впроваджена у C# 5.0, C++20, Python 3.5, Hack, Dart, Kotlin 1.1, експериментальному доповненні для Scala[1], «нічній» збірці Rust[2], і в нещодавніх версіях JavaScript. Вона дозволяє здійснити виклик асинхронного методу схожим чином, як викликаються звичайні, синхронні методи.
Базові операції
Все, що роблять ключові слова async та await, є коригуванням коду під час компіляції. Для цього використовується клас Task, який зберігає стан операції.
Приклад коду, в якому використовується await:
public async Task<int> FindPageSize(Uri uri)
{
var data = await new WebClient().DownloadDataTaskAsync(uri);
return data.Length;
}
Тут завантажується URI та повертається його довжина. Ключове слово «async» вказує компілятору, що можна переорганізувати код всередині методу, щоб дозволити відкладати відмічені виклики й обгортати їхні результати в Task. Результатом функції FindPageSize є об'єкт типу Task<int> — це означає, що функція повертає ціле число, але воно може бути неготовим для використання, щойно керування повернулося з FindPageSize — на нього потрібно «зачекати».
Тому компілятор може скорегувати цей код до такого вигляду:
public Task<int> FindPageSize(Uri uri)
{
var data_task = new WebClient().DownloadDataTaskAsync(uri);
var after_data_task = data_task.ContinueWith((original_task) => {
return original_task.Result.Length;
});
return after_data_task;
}
async у сигнатурі методу дозволяє корегування коду з використанням Task і ContinueWith при кожному використанні await. Компілятор не змінить код, якщо await не використовується (C# видає попередження), а метод не буде асинхронним.
Task можна використовувати незалежно від await. «await» є синтаксичним цукром для спрощення роботи кінцевого користувача. Тобто, при кожному використанні await решта коду, що використовує результат асинхронної функції, обгортається у блок ContinueWith.
Також, в асинхронному методі не обов'язково використовувати await. При виклику асинхронної функції без використання await задача виконуватиметься у фоновому режимі. Змінну типу Task, яку повернула функція, можна використовувати для отримання інформації про стан завдання. Таким чином, можна запустити кілька асинхронних задач одночасно, зберегти їх у списку й отримати результат, передавши його до Task.WhenAll()
. Це дозволяє їм усім виконуватися одночасно, а потім просто очікує на виконання всіх задач. Це може бути набагато ефективніше за очікування завершення кожної з них. Наприклад, виконуючи 10 мережевих запитів, якщо розпочати їх усі одночасно (а не послідовно, використовуючи await), а потім очікувати виклик WhenAll, це може призвести до паралельного виконання всіх мережевих запитів.
В С#
До сьомої версії С# асинхронні методи обов'язково мають повертати void
, Task
, або Task<T>
. Потім цей список поповнився іншими типами на кшталт ValueTask<T>
. Асинхронні методи, які нічого не повертають, призначені для обробників подій. У більшості випадків результат асинхронного методу, який нічого не повертає, рекомендується позначати як Task
, а не void
, так як це сприяє інтуїтивному керуванню виключеннями[3].
Сигнатуру методів, що використовують await
, треба позначати за допомогою async
. У методах, які повертають змінні типу Task<T>
та позначені асинхронними, має повертатися змінна типу T
, а не Task<T>
. Компілятор обгортає значення в Task<T>
. Також передбачена можливість виклику функцій, які повертають задачу, через await
, навіть якщо вони не позначені асинхронними.
Наведений нижче асинхронний метод, завантажує дані із URL використовуючи await
.
public async Task<int> SumPageSizesAsync(IList<Uri> uris)
{
int total = 0;
foreach (var uri in uris) {
statusText.Text = string.Format("Found {0} bytes ...", total);
var data = await new WebClient().DownloadDataTaskAsync(uri);
total += data.Length;
}
statusText.Text = string.Format("Found {0} bytes total", total);
return total;
}
В C++
В C++, await (який називається co_await в C++) був офіційно долучений до стандарту C++20[4]; також компілятори MSVC та Clang вже підтримують co_await.
В JavaScript
Оператор await в JavaScript може використовуватися лише в функції, що відмічена ключовим словом async. Якщо параметр є Promise-ом, виконання async-функції відновиться, коли Promise буде вирішено(resolved). Якщо Promise буде відхилий(rejected) - в цьому випадку буде викинута помилка, яка може бути оброблена з використанням звичайного сценарію виключення JavaScript. Якщо параметр не є Promise-ом, сам параметр буде повернуто негайно.[5]
Багато бібліотек надають повертають Promise-об'єкти, які також можуть використовуватися в очікуванні, якщо вони узгоджуються з специфікацією для власних Promise-об'єктів JavaScript. Проте обіцянки з бібліотеки jQuery не були сумісними з власними Promis-об'єктами на рівні A+ аж до версії jQuery 3.0.[6]
Приклад (взято з цієї статті[7]):
async function createNewDoc() {
let response = await db.post({}); // post a new doc
return await db.get(response.id); // find by id
}
async function main() {
try {
let doc = await createNewDoc();
console.log(doc);
} catch (err) {
console.log(err);
}
}()
Примітки
- scala/scala-async. GitHub (англійською). Процитовано 18 квітня 2018.
- alexcrichton/futures-await. GitHub (англійською). Процитовано 18 квітня 2018.
- Async/Await - Best Practices in Asynchronous Programming. MSDN (англійською). Процитовано 18 квітня 2018.
- ISO C++ Committee announces that C++20 design is now feature complete.
- await - JavaScript (MDN). Процитовано 2 травня 2017.
- jQuery Core 3.0 Upgrade Guide. Процитовано 2 травня 2017.
- Taming the asynchronous beast with ES7. Процитовано 12 листопада 2015.