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

Как работать с асинхронностью в React?

2.0 Middle🔥 231 комментариев
#React#Архитектура и паттерны

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

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

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

Асинхронность в React

Асинхронные операции (запросы к API, таймеры, работа с файлами) часто нужны в React компонентах. Главная проблема - управление lifecycle'ом компонента и предотвращение утечек памяти.

1. useEffect для асинхронных операций

Утечка памяти происходит если компонент размонтировался, а запрос ещё идет:

import { useEffect, useState } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let isMounted = true; // Флаг для отслеживания
    
    const fetchUser = async () => {
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        
        if (isMounted) { // Проверяем что компонент ещё в DOM
          setUser(data);
        }
      } catch (err) {
        if (isMounted) {
          setError(err.message);
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    };
    
    fetchUser();
    
    // Cleanup функция
    return () => {
      isMounted = false; // Отмечаем что компонент размонтировался
    };
  }, [userId]); // Пересчитываем при изменении userId
  
  if (loading) return <div>Загрузка...</div>;
  if (error) return <div>Ошибка: {error}</div>;
  
  return <div>{user.name}</div>;
}

2. Использование AbortController

Современный способ отмены запросов:

function DataFetcher() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    const controller = new AbortController();
    
    const fetchData = async () => {
      try {
        const response = await fetch('/api/data', {
          signal: controller.signal // Передаем сигнал отмены
        });
        const result = await response.json();
        setData(result);
      } catch (err) {
        if (err.name === 'AbortError') {
          console.log('Запрос был отменён');
        } else {
          console.error('Ошибка:', err);
        }
      }
    };
    
    fetchData();
    
    return () => {
      controller.abort(); // Отменяем запрос при размонтировании
    };
  }, []);
  
  return <div>{data ? JSON.stringify(data) : 'Нет данных'}</div>;
}

3. Асинхронные функции с async/await

function PostsList() {
  const [posts, setPosts] = useState([]);
  
  useEffect(() => {
    const loadPosts = async () => {
      try {
        // Ждём ответ от API
        const response = await fetch('/api/posts');
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        setPosts(data);
      } catch (error) {
        console.error('Ошибка загрузки постов:', error);
      }
    };
    
    loadPosts();
  }, []);
  
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

4. Обработка нескольких асинхронных операций

function Dashboard() {
  const [stats, setStats] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const controller = new AbortController();
    
    const fetchData = async () => {
      try {
        // Promise.all для параллельных запросов
        const [usersRes, postsRes, commentsRes] = await Promise.all([
          fetch('/api/users', { signal: controller.signal }),
          fetch('/api/posts', { signal: controller.signal }),
          fetch('/api/comments', { signal: controller.signal }),
        ]);
        
        const [users, posts, comments] = await Promise.all([
          usersRes.json(),
          postsRes.json(),
          commentsRes.json(),
        ]);
        
        setStats({ users, posts, comments });
      } catch (error) {
        if (error.name !== 'AbortError') {
          console.error('Ошибка:', error);
        }
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
    
    return () => controller.abort();
  }, []);
  
  return loading ? <div>Загрузка...</div> : <div>{JSON.stringify(stats)}</div>;
}

5. Дебаунсирование асинхронных запросов

function SearchUsers({ query }) {
  const [results, setResults] = useState([]);
  
  useEffect(() => {
    // Создаём таймер для дебаунса
    const timer = setTimeout(async () => {
      if (query.length === 0) {
        setResults([]);
        return;
      }
      
      try {
        const response = await fetch(`/api/search?q=${query}`);
        const data = await response.json();
        setResults(data);
      } catch (error) {
        console.error('Ошибка поиска:', error);
      }
    }, 500); // Ждём 500ms после последнего изменения
    
    return () => clearTimeout(timer); // Отменяем старый таймер
  }, [query]);
  
  return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>;
}

6. Кастомный Hook для загрузки данных

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const controller = new AbortController();
    
    const fetchData = async () => {
      try {
        const response = await fetch(url, { signal: controller.signal });
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const result = await response.json();
        setData(result);
      } catch (err) {
        if (err.name !== 'AbortError') setError(err);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
    return () => controller.abort();
  }, [url]);
  
  return { data, loading, error };
}

// Использование
function App() {
  const { data, loading, error } = useFetch('/api/users');
  
  if (loading) return <div>Загрузка...</div>;
  if (error) return <div>Ошибка: {error.message}</div>;
  
  return <div>{JSON.stringify(data)}</div>;
}

Основные правила

  1. НИКОГДА не передавай async прямо в useEffect
  2. Всегда проверяй isMounted или используй AbortController
  3. Очищай ресурсы в cleanup функции
  4. Правильно настроившь dependency array (пустой [] - один раз)
  5. Для очень сложной логики рассмотри React Query или SWR
Как работать с асинхронностью в React? | PrepBro