> Как правильно обрабатывать асинхронные функции в useEffect и почему нельзя делать колбэк асинхронным (React)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: ADV/web-engineering, Kaspersky, Сбер

Стек: React

> Пример ответа

Короткий ответ

В React нельзя делать колбэк useEffect асинхронным, потому что он должен возвращать функцию очистки или undefined, а async-функция всегда возвращает Promise. Для обработки асинхронных операций внутри useEffect используется паттерн с объявлением асинхронной функции внутри эффекта и её немедленным вызовом, с обязательной обработкой race condition через флаг отмены.

Подробное объяснение

Основная причина запрета async-колбэка в useEffect - сигнатура: эффект должен возвращать либо функцию очистки (для отписки), либо undefined. Асинхронная функция возвращает Promise, который React не умеет обрабатывать как cleanup. Это может привести к утечкам памяти и непредсказуемому поведению.

Ключевая проблема - race condition при асинхронных операциях. Если компонент размонтируется или зависимости изменятся до завершения запроса, результат может быть применён к уже неактуальному состоянию. Решается через флаг отмены (cancelled/isMounted) или AbortController.

Правильный паттерн:

  • Создать асинхронную функцию внутри useEffect
  • Вызвать её немедленно
  • Вернуть функцию очистки, которая отменяет pending-операции

На практике

В production-коде для сложных сценариев (зависимости, пагинация, отмена запросов) лучше использовать кастомные хуки (useAsync, useFetch) или библиотеки вроде React Query. Они инкапсулируют обработку race condition, кэширование и повторные запросы.

Для простых случаев достаточно паттерна с флагом отмены. При работе с fetch используйте AbortController для реальной отмены HTTP-запроса, а не только для игнорирования ответа.

Пример кода

JAVASCRIPT
// ❌ Неправильно
useEffect(async () => {
const data = await fetchData();
setState(data);
}, []);
// ✅ Правильно
useEffect(() => {
let cancelled = false;
const loadData = async () => {
const data = await fetchData();
if (!cancelled) {
setState(data);
}
};
loadData();
return () => {
cancelled = true;
};
}, []);
// ✅ С AbortController
useEffect(() => {
const controller = new AbortController();
const loadData = async () => {
try {
const data = await fetchData({ signal: controller.signal });
setState(data);
} catch (error) {
if (!controller.signal.aborted) {
setError(error);
}
}
};
loadData();
return () => controller.abort();
}, []);

Как отвечать на собеседовании

Начни с ключевой причины - сигнатура useEffect требует возврата функции очистки или undefined, а async-функция возвращает Promise. Затем объясни проблему race condition и покажи правильный паттерн с флагом отмены. Упомяни AbortController как более продвинутый вариант. Добавь, что в реальных проектах часто используют готовые решения (React Query, SWR) для избежания boilerplate-кода.

Что проверяет интервьюер

  • Понимание сигнатуры useEffect и её ограничений
  • Знание проблемы race condition в асинхронных операциях
  • Умение правильно управлять жизненным циклом эффектов
  • Осведомлённость о современных подходах (AbortController, библиотеки)
  • Способность объяснить не только "как", но и "почему"

Типичные ошибки

  • Использование async-колбэка напрямую (Promise игнорируется, нет очистки)
  • Игнорирование race condition при смене зависимостей
  • Установка состояния после размонтирования компонента
  • Отсутствие обработки ошибок в асинхронном коде внутри эффекта
  • Использование isMounted как антипаттерна (устаревший подход)

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью