Открыть сервис

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:

  1. Приостановка выполнения: Выполнение текущей асинхронной функции приостанавливается в точке, где встречается await. При этом управление возвращается вызывающему коду, а основной поток (или событийный цикл) не блокируется.
  2. Ожидание результата: Функция возобновляет своё выполнение только после того, как асинхронная операция, указанная после await, будет завершена.
  3. Извлечение результата: 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.

Критика и ограничения

  1. Заразность асинхронности: Если функция использует await, она сама должна быть объявлена как async. Это может привести к тому, что асинхронность «расползается» по всей кодовой базе, требуя изменения большого количества функций.
  2. Сложность отладки: Хотя async/await упрощает чтение кода, отладка асинхронных программ может быть сложнее из-за того, что стек вызовов не всегда очевиден (особенно в случае ошибок в промисах).
  3. Производительность: Использование async/await влечёт за собой накладные расходы на создание конечного автомата и управление состоянием. В большинстве приложений эти накладные расходы незначительны, но в высокопроизводительных системах могут быть критичны.
  4. Непонимание модели выполнения: Некоторые разработчики ошибочно полагают, что 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 →