Колбэки

Колбэки #

Колбэк (callback) — функция, которую передают другой функции, чтобы та вызвала её позже: после события, таймера или ответа сети. До появления промисов колбэки были основным способом описывать асинхронность в JavaScript.


Синхронный и асинхронный колбэк #

Синхронный колбэк выполняется сразу внутри вызывающего кода:

const playlist = ['Neon Drive', 'Static Bloom', 'Low Tide']

playlist.forEach((track, index) => {
  console.log(`${index + 1}. ${track}`)
})

Асинхронный колбэк откладывается — типичный пример setTimeout:

function showToast(message, onHidden) {
  console.log(message)
  setTimeout(() => {
    onHidden?.()
  }, 2000)
}

showToast('Трек добавлен в плейлист', () => {
  console.log('Тост скрыт')
})

Вызывающий код не ждёт завершения колбэка: выполнение идёт дальше, а результат приходит позже.


Имитация сетевого запроса #

В учебных примерах API часто моделируют таймером:

function fetchTrackMeta(trackId, callback) {
  const fakeDb = {
    'tr-01': { title: 'Neon Drive', bpm: 118 },
    'tr-02': { title: 'Static Bloom', bpm: 92 },
  }

  setTimeout(() => {
    const meta = fakeDb[trackId]
    if (!meta) {
      callback(new Error(`Трек ${trackId} не найден`), null)
      return
    }
    callback(null, meta)
  }, 300)
}

fetchTrackMeta('tr-01', (err, meta) => {
  if (err) {
    console.error(err.message)
    return
  }
  console.log(`${meta.title}, ${meta.bpm} BPM`)
})

Здесь используется распространённое соглашение Node.js: первый аргумент — ошибка (null при успехе), второй — данные.


Колбэк как завершение операции #

Браузерные API исторически строились на колбэках: addEventListener, readAsText у FileReader, старые варианты XHR. Паттерн один: «запусти операцию → вызови функцию, когда готово».

function readPlaylistFile(file, onResult) {
  const reader = new FileReader()
  reader.onload = () => onResult(null, reader.result)
  reader.onerror = () => onResult(reader.error, null)
  reader.readAsText(file)
}

Разделение onload / onerror — вариант того же принципа: разные колбэки для успеха и сбоя.


Пирамида колбэков (callback hell) #

Когда шаги зависят друг от друга, колбэки вкладывают друг в друга:

fetchTrackMeta('tr-01', (err, meta) => {
  if (err) return handle(err)

  fetchTrackMeta('tr-02', (err2, meta2) => {
    if (err2) return handle(err2)

    mergeIntoPlaylist([meta, meta2], (err3) => {
      if (err3) return handle(err3)

      renderPlaylist(() => {
        // ещё один уровень вложенности...
      })
    })
  })
})

Проблемы такого стиля:

ПроблемаПоследствие
Глубокая вложенностьТрудно читать и сопровождать
Дублирование if (err)Шум и риск пропустить проверку
Параллельные запросыСложно координировать без счётчиков
Обработка одной ошибкиНет единой точки .catch

Промисы и async/await решают те же задачи с плоской структурой и явной передачей ошибок по цепочке.


Инверсия управления #

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


Итог #

  • Колбэк — функция, переданная для вызова позже; основа событий и старых async API.
  • Соглашение (err, data) упорядочивает обработку сбоев в Node-стиле.
  • Вложенные зависимые шаги приводят к «пирамиде» и дублированию проверок.
  • Промисы и async/await — эволюция той же идеи с более читаемым синтаксисом.

См. также #

Часто спрашивают

Что выбрать — then или async/await?
async/await обычно читается проще для последовательного кода; then удобен для коротких цепочек и комбинирования с Promise API. Подробнее
Чем Promise.all отличается от allSettled?
all падает при первой ошибке; allSettled дожидается всех и отдаёт статус каждого результата. Подробнее