Gernar
Frontend DeveloperJavaScript

Интервью-вопрос

Callback является функцией или объектом

Callback это функция, которую передают для последующего вызова. Технически функция в JavaScript является объектом, но на практике важно говорить о роли callback, времени вызова и обработке ошибок.

Добавлен
Редакция

Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.

🐰0
🥚0

Мини-квиз

Проверка перед разбором

Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.

Вопрос 1 из 50 правильно

Как лучше ответить на вопрос, callback это функция или объект?

Вы хотите дать короткий, но точный ответ на интервью.

Варианты ответа

Разбор

Разобраться, а не зазубрить

Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.

Базовая идея

Callback это не отдельный тип данных. Это роль функции в коде. Функция становится callback, когда вы передаете ее в другой код и ожидаете, что этот другой код вызовет ее в нужный момент.

button.addEventListener("click", () => {
  console.log("Клик обработан");
});

Функция внутри addEventListener это callback. Она не выполняется сразу при чтении строки. Браузер вызовет ее позже, когда пользователь нажмет кнопку.

Почему тогда говорят про объект

В JavaScript функции являются объектами первого класса. Это значит, что функцию можно положить в переменную, передать в аргумент, вернуть из другой функции и добавить ей свойства.

function onDone(result) {
  console.log(result);
}

console.log(typeof onDone); // "function"
console.log(onDone.name); // "onDone"

typeof для функции вернет "function". При этом функция имеет свойства и методы, например name, length, call, apply, bind. Поэтому точная формулировка такая: callback это функция по назначению, а функция в JavaScript технически является объектом.

Как построить ответ

1Вас спрашивают, функция это или объект?
Начните с роли: callback это функция для последующего вызова.
2Нужно уточнить техническую сторону языка?
Добавьте, что функции в JavaScript являются объектами первого класса.
3Вопрос уходит в практику асинхронности?
Говорите про момент вызова, ошибки, повторный вызов и устаревшие данные.
4Нужно сравнить с современным кодом?
Объясните, что Promise и async/await часто удобнее для цепочек и обработки ошибок.

Практический риск для фронтенда

На практике вопрос не только про термин. Важно понимать, кто управляет вызовом callback. Если вы передали функцию в браузерный API, библиотеку или свой helper, вы уже не вызываете ее напрямую. Это влияет на ошибки, состояние UI и очистку ресурсов.

Плохой пример:

try {
  setTimeout(() => {
    throw new Error("Не удалось обновить данные");
  }, 1000);
} catch (error) {
  console.log("Поймали ошибку", error);
}

Этот catch не поймает ошибку из таймера, потому что callback выполнится позже, когда внешний стек уже завершился. Безопаснее обработать ошибку внутри callback или использовать Promise API, где ошибка попадет в catch или try/catch вокруг await.

Более безопасная версия для callback выглядит так:

setTimeout(() => {
  try {
    throw new Error("Не удалось обновить данные");
  } catch (error) {
    console.error(error);
    showErrorMessage("Попробуйте еще раз");
  }
}, 1000);

В реальном UI вместо простого console.error обычно нужно закрыть loader, показать понятную ошибку и не оставлять экран в подвешенном состоянии.

Callback в своем API

Если вы сами пишете функцию, которая принимает callback, договоритесь о контракте. Минимум нужно понимать, какие аргументы получит callback, сколько раз он может быть вызван и как передается ошибка.

function loadUser(userId, callback) {
  fetch(`/api/users/${userId}`)
    .then((response) => {
      if (!response.ok) {
        throw new Error("User loading failed");
      }
      return response.json();
    })
    .then((user) => callback(null, user))
    .catch((error) => callback(error, null));
}

Здесь используется старый стиль error-first callback: первым аргументом идет ошибка, вторым данные. Такой контракт надо проговаривать явно. Иначе вызывающий код может принять ошибку за данные или наоборот.

Безопасный контракт callback
  1. 1Проверить, что аргумент является функцией
  2. 2Описать порядок аргументов
  3. 3Вызвать callback один раз для одного результата
  4. 4Передать ошибку явно или обработать ее внутри
  5. 5Не обновлять UI после отмененной операции
Опасный callback API
  1. 1Принять любой аргумент и сразу вызвать его
  2. 2Смешать данные и ошибку без понятного правила
  3. 3Вызвать callback несколько раз случайно
  4. 4Проигнорировать Promise из async callback
  5. 5Оставить обработчики без cleanup

Callback, Promise и async/await

Callback и Promise часто встречаются рядом, но это разные модели. Callback это функция, которую кто-то должен вызвать. Promise это объект, который представляет будущий результат операции и имеет состояние: ожидание, успех или ошибка.

Если код становится вложенным, Promise и async/await обычно читаются проще:

async function showUser(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      throw new Error("User loading failed");
    }

    const user = await response.json();
    renderUser(user);
  } catch (error) {
    showErrorMessage("Не удалось загрузить пользователя");
  }
}

Это не значит, что callback устарел везде. События DOM, таймеры, некоторые браузерные API и legacy-библиотеки все еще используют callback. Сильный ответ показывает, что вы понимаете оба подхода и выбираете их по контракту, а не по моде.

Нюанс с async callback

async функция может быть callback, потому что это все еще функция. Но результат такой функции всегда Promise. Проблема начинается, если вызывающий API этот Promise не ожидает.

element.addEventListener("click", async () => {
  await saveAnalyticsEvent();
  showSuccessMessage();
});

Такой код может быть нормальным, если ошибка внутри обработана или некритична. Но браузерный addEventListener не будет ждать Promise как часть своего контракта. Если saveAnalyticsEvent упадет и вы не поймаете ошибку, пользователь может не увидеть обратную связь, а в консоли появится необработанная ошибка.

Опасный вариант в UI: добавить callback как обработчик и не снять его при cleanup.

function subscribeButton(button) {
  button.addEventListener("click", () => {
    sendAnalytics("button_click");
  });
}

Если такую функцию вызвать несколько раз, один клик отправит несколько событий. Если элемент или экран уже неактуален, старый callback все равно может сработать. Безопаснее хранить ссылку на обработчик и снимать ее, когда подписка больше не нужна.

function subscribeButton(button) {
  const onClick = () => {
    sendAnalytics("button_click");
  };

  button.addEventListener("click", onClick);

  return () => {
    button.removeEventListener("click", onClick);
  };
}

Практический вывод

На интервью лучше не спорить, выбрать одно слово или другое. Дайте формулировку, которая закрывает обе стороны вопроса:

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

Такой ответ звучит точнее, чем просто "функция" или просто "объект". Он показывает, что вы понимаете и язык, и практические последствия для frontend-кода.

Частые ошибки

Где обычно ошибаются

Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.

  1. 1

    Называть callback обычным объектом

    Так вы теряете главный смысл. Callback важен не потому, что у функции есть свойства, а потому что ее передают для вызова в нужный момент. Скажите: callback это функция, а в JavaScript функции технически являются объектами.
  2. 2

    Путать callback и Promise

    Promise сам хранит состояние операции, а callback просто вызывается чужим кодом. Если смешать эти понятия, легко ошибиться в обработке ошибок и ожидании результата. На практике это приводит к зависшим загрузчикам и необработанным ошибкам.
  3. 3

    Думать, что async callback всегда ожидается

    async callback возвращает Promise, но не каждый API его использует. Например, обработчик события в браузере не будет ждать ваш await как часть своего контракта. Если ошибка внутри не обработана, она может стать unhandled rejection.
  4. 4

    Не говорить про момент вызова

    Callback может быть вызван синхронно, после таймера, после ответа сети или при событии пользователя. Без этого уточнения ответ звучит как определение из учебника. Для фронтенда момент вызова влияет на состояние UI, cleanup и гонки данных.
  5. 5

    Не проверять контракт callback API

    Если функция принимает callback, важно понимать, какие аргументы она передает и сколько раз вызывает callback. Иначе можно дважды показать toast, дважды отправить аналитику или обновить состояние после размонтирования компонента.
  6. 6

    Забывать cleanup для callback из UI API

    Обработчики событий, таймеры и подписки живут дольше одной функции. Если не снять их при уходе со страницы или размонтировании компонента, старый callback может обновить неактуальный UI, отправить лишний запрос или удерживать данные в памяти.

Follow-up

Что могут спросить дальше

Короткие ответы на вопросы, которыми интервьюер проверяет понимание callback, функций и асинхронного кода.

Живые ответы

Видео с похожим вопросом

Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.

Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.

Содержание