← Назад к вопросам

Когда нельзя использовать async?

2.2 Middle🔥 161 комментариев
#JavaScript Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когда нельзя использовать async?

Функции с ключевым словом async и оператор await являются фундаментальными инструментами современного JavaScript для работы с асинхронными операциями, делая код более линейным и читаемым. Однако их применение не всегда целесообразно или даже возможно. Нельзя использовать async/await в следующих ключевых случаях:

1. В синхронных контекстах и при необходимости мгновенного результата

async функция всегда возвращает Promise, даже если внутри не используется await. Это означает, что ее результат нельзя получить синхронно, мгновенно.

async function getSyncData() {
    return 42; // Возвращает Promise<number>, а не число
}

const result = getSyncData(); // result - это Promise, а не 42
console.log(result); // Promise {<fulfilled>: 42}
// Нельзя сразу использовать как число: result * 2 — ошибка.

Если вам нужен синхронный, немедленный результат (например, простые вычисления, преобразование данных), использование обычной функции предпочтительнее. async создает микротаски и накладные расходы на управление промисом.

2. В функциях-конструкторах и при использовании new

Функция-конструктор, вызываемая с оператором new, должна возвращать новый экземпляр объекта. async функция возвращает Promise, что нарушает этот контракт.

async function AsyncConstructor(name) {
    this.name = name;
    return this; // Бесполезно, так как возвращается Promise
}

try {
    const obj = new AsyncConstructor('Test'); // obj будет Promise, а не объектом!
    console.log(obj); // Promise {<fulfilled>: AsyncConstructor}
} catch(e) {
    // Не приведет к ошибке, но результат бессмысленный.
}

Для создания объектов с асинхронной инициализацией используют другие паттерны (например, статический асинхронный метод create).

3. В колбэках, требующих синхронного возврата определенного типа

Многие API и библиотеки ожидают, что колбэк функция вернет конкретный тип данных синхронно (например, boolean, string, object). Если такой колбэк сделать async, он вернет Promise, что приведет к ошибкам.

Пример с Array.prototype.filter:

const numbers = [1, 2, 3, 4];
// filter ожидает синхронного возврата boolean для каждого элемента
async function asyncFilter(num) {
    await Promise.resolve(); // Некая асинхронная операция
    return num % 2 === 0;
}

const filtered = numbers.filter(asyncFilter); // filtered будет массивом Promise<boolean>, а не отфильтрованным массивом чисел!
console.log(filtered); // [1, 2, 3, 4] - НЕ фильтруется!

Для подобных случаев необходимо сначала выполнить все асинхронные операции (например, с помощью map и Promise.all), а затем применять синхронный filter.

4. В событиях и некоторых жизненных циклах, где возвращаемое значение игнорируется

В обработчиках событий (например, addEventListener('click', handler) или React-подобных onClick) возвращаемое значение обычно игнорируется. Делать такой обработчик async возможно, но часто не имеет смысла и может привести к незаметным ошибкам, если вы ожидаете, что возвращаемое значение будет использовано (например, для предотвращения события). Сам событийный механизм не будет "ждать" (await) вашего промиса.

5. Когда требуется максимальная производительность в высоконагруженных синхронных циклах

Внутри async функции использование await приостанавливает ее выполнение до разрешения промиса. Если у вас есть высокочастотный цикл (например, обработка тысяч элементов в реальном времени), где каждый шаг зависит от асинхронной операции, последовательное использование await внутри цикла может создать существенные накладные расходы и замедлить выполнение из-за постоянного переключения контекста.

async function processAll(items) {
    for (const item of items) {
        // Каждый await - это приостановка и создание микротаски
        const result = await costlyAsyncOperation(item);
        // Это может быть МЕДЛЕННО для тысяч элементов.
    }
}

В таких случаях часто более эффективно использовать Promise.all() для параллельного запуска операций или пересмотреть архитектуру на использование потоков (Web Workers) или более эффективных асинхронных паттернов.

6. Вверх по цепочке вызовов без поддержки асинхронности

Если вся ваша текущая архитектура или внешний API построены на синхронном взаимодействии, внедрение async функции в середину может потребовать переписывания всех вышестоящих уровней для поддержки промисов. Это может быть невозможно или нецелесообразно. Например, при интеграции с некоторыми старыми библиотеками или системами, где интерфейсы строго синхронные.

Ключевые принципы и выводы

  • async преобразует функцию: Любая функция, объявленная как async, возвращает Promise. Это ее фундаментальное свойство.
  • Синхронный контракт: Если вызывающий код или API ожидает синхронного возврата конкретного типа (не Promise), использовать async нельзя — это нарушит контракт и приведет к ошибкам.
  • Производительность: В сценариях, где важна максимальная синхронная производительность или низкие накладные расходы, стоит избегать async/await внутри интенсивных циклов.
  • Смысловое соответствие: Используйте async только там, где есть настоящая асинхронная операция (запрос к сети, чтение файла, ожидание таймера, взаимодействие с базой данных). Не делайте функцию async просто "на будущее" или для красоты — это добавляет ненужную сложность.

Таким образом, выбор между async и синхронным кодом должен быть основан на понимании контракта функции, требований производительности и архитектуры приложения. async/await — мощный инструмент, но не серебряная пуля для всех случаев.