Асинхронность в JavaScript — это как готовка на нескольких конфорках: пока варится суп, ты режешь салат, и всё это одновременно. Но если не знать правил, можно устроить пожар на кухне. Эта статья для тех, кто хочет разобраться с колбэками, промисами и async/await без головной боли.

Digital-студия WNDER

Почему синхронный код — это скучно и медленно

JavaScript — однопоточный язык. Это значит, что он может выполнять только одну задачу за раз. Если ты напишешь код, который ждёт ответа от сервера 5 секунд, вся страница зависнет на эти 5 секунд. Пользователь увидит белый экран и уйдёт. Поэтому и придумали асинхронность: запускаем долгую операцию, не блокируя основной поток, и продолжаем работать.

Представь, что ты заказываешь пиццу. Ты не стоишь у двери, пока её готовят. Ты занимаешься своими делами, а когда курьер звонит — открываешь. Это и есть асинхронность.

Колбэки: первый блин комом

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


setTimeout(function() {
  console.log('Прошло 2 секунды');
}, 2000);

Проблема в том, что если таких операций много и они зависят друг от друга, получается «callback hell» — адская пирамида из вложенных функций. Код становится нечитаемым, а ошибки ловить всё сложнее.

Правило: если видишь больше двух уровней вложенности колбэков — остановись и перепиши на промисы.

Промисы: обещание, которое сдерживают

Промис (Promise) — это объект, который представляет результат асинхронной операции. Он может быть в трёх состояниях: ожидание (pending), выполнено (fulfilled) или отклонено (rejected). Работать с ним удобнее: можно вызывать .then() для успеха и .catch() для ошибок.


fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Ошибка:', error));

Цепочки .then() читаются сверху вниз, как обычный код. Но и у промисов есть свои подводные камни: например, легко забыть про возврат значения или перепутать порядок.

Async/await: синтаксический сахар для промисов

С выходом ES2017 появились async-функции и ключевое слово await. Они позволяют писать асинхронный код так, будто он синхронный. Это самый читаемый и удобный способ на сегодня.


async function getData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Ошибка:', error);
  }
}

Внутри async-функции можно использовать await перед любым промисом. Код становится линейным и понятным. Но важно помнить: await приостанавливает выполнение только внутри async-функции, не блокируя весь поток.

Лайфхак: если нужно выполнить несколько промисов параллельно, используй Promise.all(). Это сэкономит время.

const [users, posts] = await Promise.all([
  fetch('/users').then(res => res.json()),
  fetch('/posts').then(res => res.json())
]);

Сравнение подходов: что выбрать

Ниже таблица, которая поможет быстро определиться с инструментом.

Подход Плюсы Минусы
Колбэки Простота для одной операции Адская вложенность, сложно ловить ошибки
Промисы Цепочки, встроенная обработка ошибок Много .then(), легко запутаться
Async/await Читаемость, синхронный стиль, try/catch Нужно понимать промисы, нельзя использовать в глобальном контексте (без модулей)

Обработка ошибок: где подстелить соломку

В асинхронном коде ошибки могут возникнуть в любом месте. В колбэках их обычно передают первым аргументом (error-first callback). В промисах — используют .catch(). В async/await — обычный try/catch.

Но есть нюанс: если забыть обработать отклонённый промис, можно получить UnhandledPromiseRejection. Это как забыть выключить утюг — вроде мелочь, а пожар возможен.

Правило: всегда добавляй .catch() в конце цепочки промисов или оборачивай await в try/catch. Не оставляй ошибки без внимания.

Практические советы для реальных проектов

  • Используй async/await везде, где возможно. Это стандарт современного кода.
  • Для параллельных запросов применяй Promise.all() или Promise.allSettled().
  • Не забывай про отмену запросов: AbortController тебе в помощь.
  • Избегай смешивания колбэков и промисов в одном модуле — это сбивает с толку.
  • Пиши маленькие async-функции, которые делают одну вещь. Так проще тестировать.

Вот пример с AbortController, который позволяет отменить fetch, если пользователь ушёл со страницы:


const controller = new AbortController();
const signal = controller.signal;

fetch('/data', { signal })
  .then(response => response.json())
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Запрос отменён');
    }
  });

// Отмена через 1 секунду
setTimeout(() => controller.abort(), 1000);

Что в итоге

Асинхронность в JavaScript — это не страшно, если знать инструменты. Колбэки уходят в прошлое, промисы дают гибкость, а async/await делает код чистым и понятным. Главное — не забывать про обработку ошибок и параллельное выполнение, где это уместно. Экспериментируй, пиши код, и асинхронность станет твоим другом, а не врагом.

Студия WNDER