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

Через что работаешь с ассинхронностью

2.0 Middle🔥 171 комментариев
#JavaScript Core

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

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

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

Работа с асинхронностью в JavaScript

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

Основные механизмы и паттерны

1. Callback функции

Это исторически первый и базовый механизм. Функция передается как аргумент в асинхронную операцию и выполняется по её завершении.

// Пример с callback
fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Ошибка чтения:', err);
    return;
  }
  console.log('Содержимое файла:', data);
});

Проблема: "Callback Hell" — глубоко вложенные колбэки, которые сложно читать и поддерживать.

2. Промисы (Promises)

Промисы представляют будущее значение и предоставляют цепочный интерфейс .then()/.catch().

// Пример с Promise
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => {
    console.log('Полученные данные:', data);
    return processData(data);
  })
  .then(processed => saveToStorage(processed))
  .catch(error => console.error('Ошибка:', error));

Ключевые преимущества:

  • Линейный поток чтения кода
  • Централизованная обработка ошибок через .catch()
  • Возможность комбинации через Promise.all(), Promise.race()

3. Async/Await

Синтаксический сахар над промисами, позволяющий писать асинхронный код в синхронном стиле.

// Пример с async/await
async function loadUserData() {
  try {
    const response = await fetch('https://api.example.com/user');
    const user = await response.json();
    
    const postsResponse = await fetch(`https://api.example.com/posts/${user.id}`);
    const posts = await postsResponse.json();
    
    return { user, posts };
  } catch (error) {
    console.error('Ошибка загрузки:', error);
    throw error;
  }
}

Преимущества:

  • Максимальная читаемость кода
  • Естественная обработка ошибок через try/catch
  • Возможность использования в циклах с for-await-of

Современные практики и инструменты

Параллельное выполнение

// Параллельная загрузка данных
async function loadDashboard() {
  const [user, products, notifications] = await Promise.all([
    fetchUser(),
    fetchProducts(),
    fetchNotifications()
  ]);
  
  return { user, products, notifications };
}

Обработка состояний загрузки и ошибок

В современных фреймворках я использую состояния загрузки в сочетании с асинхронностью:

// Пример в React-компоненте
function DataComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    loadData();
  }, []);
  
  async function loadData() {
    try {
      setLoading(true);
      const result = await apiService.fetchData();
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }
  
  if (loading) return <Loader />;
  if (error) return <ErrorMessage message={error} />;
  
  return <DataView data={data} />;
}

Продвинутые техники

Отмена асинхронных операций

Для предотвращения утечек памяти и ненужных операций:

// Использование AbortController
const controller = new AbortController();

async function fetchWithTimeout(url, timeout =3535000) {
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeoutId);
    return response;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Запрос отменен');
    }
    throw error;
  }
}

Обработка потоковых данных

Для работы с большими объемами данных или real-time потоками:

// Пример с ReadableStream
async function processStream(response) {
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    
    const chunk = decoder.decode(value);
    // обработка чанка данных
    console.log('Получен чанк:', chunk);
  }
}

Архитектурные подходы

В крупных приложениях я применяю:

  • React Query / SWR для управления серверным состоянием с кэшированием
  • Redux с middleware (redux-thunk, redux-saga) для сложных асинхронных workflows
  • Собственные хуки для инкапсуляции асинхронной логики в React
  • Service Workers для фоновой синхронизации и офлайн-[режима]

Вывод

Сегодня async/await стал де-[факто] стандартом для большинства асинхронных операций благодаря балансу читаемости и функциональности. Однако понимание всех уровней абстракции — от колбэков до промисов и современных абстракций — критически важно для:

  • Отладки сложных асинхронных цепочек
  • Оптимизации производительности
  • Обработки краевых случаев
  • Создания отказоустойчивых приложений

Я выбираю подход в зависимости от контекста: простые запросы — async/await, параллельные операции — Promise.all(), сложные workflows — комбинация паттернов с возможностью отмены и повторных попыток.

Через что работаешь с ассинхронностью | PrepBro