Async/await¶
Async/await — это синтаксическая конструкция в языках программирования, предназначенная для упрощения работы с асинхронным кодом. Она позволяет писать асинхронные операции в стиле, похожем на синхронный, что делает код более читаемым и понятным по сравнению с использованием цепочек колбэков (callback hell) или сложных конструкций на основе промисов (promises). Ключевые слова async и await были впервые введены в языке C# (версия 5.0, 2012 год) и впоследствии были реализованы во многих других языках, включая JavaScript (ES2017), Python (3.5), Rust, Kotlin, Dart, Swift и другие.
¶История
До появления async/await асинхронное программирование в большинстве языков было сложным для восприятия. В JavaScript, например, для обработки асинхронных операций (запросы к серверу, таймеры, работа с файлами) использовались функции обратного вызова (колбэки). Это приводило к так называемому «аду колбэков» — глубоко вложенным структурам, которые было трудно читать и отлаживать. В 2015 году в стандарте ECMAScript 2015 (ES6) были введены промисы, которые предоставили более гибкий механизм для работы с асинхронностью, но всё ещё требовали цепочек вызовов .then() и .catch(), что не всегда было интуитивно понятно.
В 2012 году Microsoft представила async/await в C# 5.0. Эта конструкция была вдохновлена языком F# и его асинхронными рабочими процессами. Идея заключалась в том, чтобы компилятор автоматически преобразовывал код, помеченный ключевым словом async, в конечный автомат, который приостанавливает выполнение метода при встрече await и возобновляет его после завершения асинхронной операции. Успех этой модели в C# привёл к её быстрому распространению.
В JavaScript async/await была добавлена в стандарт ECMAScript 2017 (ES8). Реализация опиралась на уже существующие промисы: функция, объявленная с ключевым словом async, всегда возвращает промис, а ключевое слово await может быть использовано только внутри такой функции и приостанавливает её выполнение до разрешения промиса.
¶Принцип работы
¶Ключевое слово async
Ключевое слово async ставится перед объявлением функции. Оно указывает компилятору или интерпретатору, что данная функция является асинхронной. Основное следствие использования async:
- Функция всегда возвращает объект, представляющий асинхронную операцию (в JavaScript —
Promise, в C# —TaskилиTask<T>, в Python —coroutine). - Внутри такой функции можно использовать ключевое слово
await.
Если асинхронная функция возвращает какое-либо значение, оно автоматически упаковывается в соответствующий асинхронный объект. Если функция не возвращает явного значения, возвращается асинхронный объект, который разрешается как undefined (в JavaScript) или void (в C#).
¶Ключевое слово await
Ключевое слово await используется только внутри функций, объявленных с async. Оно ставится перед выражением, которое представляет собой асинхронную операцию (например, перед вызовом другой асинхронной функции, возвращающей промис или задачу).
Действие await:
- Приостановка выполнения: Выполнение текущей асинхронной функции приостанавливается в точке, где встречается
await. При этом управление возвращается вызывающему коду, а основной поток (или событийный цикл) не блокируется. - Ожидание результата: Функция возобновляет своё выполнение только после того, как асинхронная операция, указанная после
await, будет завершена. - Извлечение результата:
awaitизвлекает значение, с которым разрешилась асинхронная операция, и присваивает его переменной (если это необходимо). В случае ошибкиawaitвыбрасывает исключение, которое можно обработать стандартным блокомtry...catch.
¶Пример (JavaScript)
```javascript // Асинхронная функция, имитирующая запрос к серверу function fetchData() { return new Promise((resolve) => { setTimeout(() => { resolve("Данные получены"); }, 1000); }); }
// Использование async/await async function displayData() { console.log("Начало запроса..."); const result = await fetchData(); // Выполнение приостанавливается на 1 секунду console.log(result); // "Данные получены" console.log("Запрос завершён"); }
displayData(); console.log("Этот код выполняется сразу, не дожидаясь displayData"); ```
В этом примере console.log("Этот код выполняется сразу...") будет выполнен до того, как displayData завершит свою работу, так как await не блокирует основной поток, а лишь приостанавливает выполнение самой асинхронной функции.
¶Обработка ошибок
Одним из главных преимуществ async/await является возможность обработки ошибок с помощью стандартных конструкций try...catch. Это делает код более чистым по сравнению с цепочками .catch() в промисах.
``javascript async function riskyOperation() { try { const data = await fetchData(); // Если fetchData выбросит ошибку console.log(data); } catch (error) { console.error("Произошла ошибка:", error); } } ``
Если внутри асинхронной функции возникает необработанное исключение, возвращаемый промис (или задача) переходит в состояние «отклонено» (rejected). Это позволяет обрабатывать ошибки на более высоком уровне.
¶Сравнение с другими подходами
¶Синхронный код
Синхронный код выполняется последовательно, блокируя поток до завершения каждой операции. Это делает его простым для понимания, но непригодным для длительных операций (например, сетевых запросов), так как это «заморозит» пользовательский интерфейс.
¶Колбэки
Колбэки — это функции, передаваемые в качестве аргументов другим функциям и вызываемые после завершения асинхронной операции. Главный недостаток — «ад колбэков», когда код становится вложенным и трудночитаемым.
¶Промисы
Промисы представляют собой объекты, которые представляют будущий результат асинхронной операции. Они позволяют выстраивать цепочки вызовов с помощью методов .then() и .catch(). Это решает проблему «ада колбэков», но код всё ещё может быть менее читаемым, чем синхронный, особенно при сложной логике с множеством последовательных и параллельных операций.
¶Async/await
Async/await — это синтаксический сахар над промисами (или задачами). Он не вводит новую концепцию, а лишь предоставляет более удобный способ работы с существующими асинхронными механизмами. Код выглядит как синхронный, но выполняется асинхронно.
¶Особенности в разных языках
¶JavaScript
asyncфункция всегда возвращаетPromise.awaitработает только сPromise(или thenable-объектами).awaitможно использовать только внутриasyncфункции (на верхнем уровне модулей в некоторых средах — с оговорками).
¶C#
asyncметод возвращаетTask,Task<T>илиvoid(не рекомендуется для асинхронных методов, кроме обработчиков событий).awaitработает с любым объектом, реализующим шаблонGetAwaiter.awaitне может быть использован в методеMain(до C# 7.1) без специальной обёртки.
¶Python
async defопределяет корутину (coroutine).awaitприостанавливает корутину до получения результата от другой корутины или асинхронного итератора.- Для запуска корутин требуется событийный цикл (event loop), например,
asyncio.run().
¶Rust
async fnвозвращаетFuture.await— это постфиксный оператор (.await), который приостанавливает выполнение до завершенияFuture.- Для выполнения
Futureтребуется исполнитель (executor), например,tokioилиasync-std.
¶Критика и ограничения
- Заразность асинхронности: Если функция использует
await, она сама должна быть объявлена какasync. Это может привести к тому, что асинхронность «расползается» по всей кодовой базе, требуя изменения большого количества функций. - Сложность отладки: Хотя
async/awaitупрощает чтение кода, отладка асинхронных программ может быть сложнее из-за того, что стек вызовов не всегда очевиден (особенно в случае ошибок в промисах). - Производительность: Использование
async/awaitвлечёт за собой накладные расходы на создание конечного автомата и управление состоянием. В большинстве приложений эти накладные расходы незначительны, но в высокопроизводительных системах могут быть критичны. - Непонимание модели выполнения: Некоторые разработчики ошибочно полагают, что
awaitблокирует весь поток, что приводит к неправильному использованию и потенциальным проблемам с производительностью.
¶Источники
- Спецификация ECMAScript 2017 (ES8) — раздел «Async Functions».
- Документация Microsoft C# — «Asynchronous programming with async and await».
- Документация Python — «Coroutines and Tasks» (asyncio).
- Книга «The Rust Programming Language» — глава «Async and Await».
- Статья «Async/Await: The Good, the Bad, and the Ugly» (Various authors).
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →

