Интервью-вопрос
Что сработает первое, setInterval или Promise
В одном синхронном участке кода Promise.then выполнится раньше ближайшего callback setInterval. Но сильный ответ должен уточнить контекст, текущий стек вызовов и риск задержки таймеров длинными микрозадачами.
- Добавлен
- Редакция
Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.
Мини-квиз
Проверка перед разбором
Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.
Вопрос 1 из 50 правильно
Разбор
Разобраться, а не зазубрить
Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.
Базовая идея
Короткий ответ: в типичном примере первым сработает Promise.then, а не setInterval. Причина не в том, что Promise быстрее. Причина в том, что браузер по-разному планирует эти callback.
Promise.then добавляет микрозадачу. setInterval регистрирует таймер, callback которого будет выполняться как задача таймера. Когда текущий синхронный код закончился, браузер делает microtask checkpoint. Он выполняет микрозадачи до опустошения очереди. Только после этого он может перейти к следующей задаче, например к timer callback.
Простой пример для ответа:
setInterval(() => {
console.log("interval");
}, 0);
Promise.resolve().then(() => {
console.log("promise");
});
console.log("sync");Ожидаемый порядок первых логов в браузере будет таким: sync, потом promise, потом interval. Синхронный код идет первым, потому что микрозадачи не прерывают текущий call stack. После него выполняется микрозадача Promise. И только затем браузер добирается до задачи таймера.
Как аккуратно сформулировать на интервью
Хорошая формулировка может звучать так:
Если речь про браузер и оба callback запланированы из одного синхронного участка кода, раньше выполнится Promise.then. Он попадает в очередь микрозадач. Callback setInterval будет задачей таймера. После текущего стека браузер сначала выполнит все микрозадачи и только потом перейдет к таймерам.
Такая фраза лучше, чем просто "Promise всегда первый". Она показывает границы ответа: браузер, один синхронный участок кода, текущий стек и ближайшая задача таймера.
- 1Выполняется текущий script или handler
- 2setInterval регистрирует таймер
- 3Promise.then добавляет микрозадачу
- 4После стека выполняются все микрозадачи
- 5Потом браузер берет задачу таймера
- 1Браузер уже запустил callback interval
- 2Синхронный код callback выполняется первым
- 3Promise.then внутри callback добавляет микрозадачу
- 4После callback выполняется эта микрозадача
- 5Следующий тик interval ждет своей очереди
Главная ловушка
Опасная ошибка: сказать, что Promise выполняется синхронно. Это неверно. Синхронно создается Promise и регистрируется обработчик, но сам callback then выполняется позже, как микрозадача.
Еще одна ловушка: фраза "микрозадачи выполняются перед всем остальным". Они выполняются перед следующей задачей, но не посреди уже выполняющейся функции. Поэтому синхронные console.log, вычисления и текущий обработчик события закончатся раньше микрозадач.
Пример, где правило "Promise первый" нужно уточнить:
setInterval(() => {
console.log("interval start");
Promise.resolve().then(() => {
console.log("promise inside interval");
});
console.log("interval end");
}, 1000);Внутри уже запущенного callback порядок будет таким: interval start, interval end, promise inside interval. Promise не прерывает текущий callback. Но микрозадача выполнится до того, как браузер возьмет следующую задачу.
Практический вывод для фронтенда
Для собеседования важно связать ответ с интерфейсом. setInterval не гарантирует точное время выполнения. Его callback может задержаться, если главный поток занят синхронной работой, длинной очередью микрозадач, обработкой событий или если вкладка работает в фоне.
Если вы делаете счетчик, polling или обновление UI, не стройте логику так, будто interval сработает идеально каждые N миллисекунд. Проверяйте реальное время, очищайте таймеры и учитывайте, что async-операции могут завершаться не в том порядке, в котором стартовали.
Что выбрать во фронтенд-коде
setIntervalНе ждите точной периодичности. Интервал может запаздывать, а новый тик может стартовать до завершения старого запроса.
Promise / queueMicrotaskПодходит для быстрых действий после текущего кода. Тяжелую работу лучше не держать в микрозадачах.
requestAnimationFrameДля обновления кадра лучше синхронизироваться с рендером, а не надеяться на частый interval.
clearIntervalИнтервал нужно очищать при размонтировании, иначе будут лишние вызовы, утечки и обновления неактуального состояния.
Плохой пример для компонента: интервал запускается, но никогда не очищается. Еще хуже, если каждый тик отправляет запрос и не ждет завершения предыдущего.
// Плохо: interval продолжит жить после размонтирования компонента
useEffect(() => {
setInterval(() => {
refreshData();
}, 5000);
}, []);Так можно получить лишние запросы, обновление старого состояния и утечку. Если запросы идут дольше интервала, ответы могут прийти не по порядку. Старый ответ перезапишет новые данные, и UI покажет неактуальное состояние.
Безопаснее сохранить id, очистить таймер, не запускать второй запрос поверх первого и отменить активный запрос при cleanup:
useEffect(() => {
let ignore = false;
let inFlight = false;
let controller;
const load = async () => {
if (inFlight) return;
inFlight = true;
controller = new AbortController();
try {
const data = await fetchItems({ signal: controller.signal });
if (!ignore) {
setItems(data);
}
} finally {
inFlight = false;
}
};
load();
const id = setInterval(load, 5000);
return () => {
ignore = true;
controller?.abort();
clearInterval(id);
};
}, [fetchItems]);Если ваша функция запроса не принимает AbortSignal, используйте флаг актуальности или номер запроса и игнорируйте устаревший результат. Главное не давать старому async-результату менять состояние после ухода со страницы или после более свежего ответа.
Когда микрозадачи могут навредить
Микрозадачи удобны для короткой работы, которую нужно выполнить сразу после текущего кода. Но если вы создаете цепочку, где каждая микрозадача добавляет следующую, браузер будет продолжать очищать очередь микрозадач и может долго не перейти к таймерам, событиям и рендеру.
function loop() {
Promise.resolve().then(loop);
}
// Плохо: такая цепочка может не дать браузеру перейти к таймерам и рендеру
loop();На практике это может выглядеть как зависшая страница. Поэтому в ответе полезно сказать: высокий приоритет микрозадач не означает, что туда нужно складывать тяжелую работу.
Частые ошибки
Где обычно ошибаются
Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.
- 1
Говорить, что Promise выполняется синхронно
Обработчикthenне выполняется прямо в момент вызоваPromise.resolve().then(...). Он попадает в очередь микрозадач и запускается после текущего синхронного кода. Если назвать его синхронным, вы неправильно объясните порядок логов и можете ошибиться на follow-up. - 2
Игнорировать текущий стек вызовов
Микрозадачи не прерывают уже выполняющийся JavaScript. Сначала закончится текущая функция или обработчик события, и только потом браузер выполнитPromise.then. Без этого легко ожидать, что Promise вклинится прямо между двумя синхронными строками. - 3
Считать setInterval точными часами
setIntervalзадает минимальную задержку между попытками поставить callback в выполнение, но не гарантирует точное время. Если поток занят, микрозадач много или вкладка в фоне, callback придет позже. Для UI это может дать рывки счетчика, неверные тайминги и плохой UX. - 4
Создавать бесконечную цепочку микрозадач
Если каждая микрозадача добавляет следующую, браузер может долго не добраться до таймеров, событий и рендера. На практике это выглядит как зависший интерфейс. Длинную работу лучше дробить осознанно и иногда отдавать управление браузеру через задачу или специализированный API. - 5
Не очищать интервал в компоненте
В React или другом UI-коде забытыйclearIntervalпродолжит вызывать callback после ухода со страницы или размонтирования компонента. Это приводит к лишним запросам, утечкам и обновлениям неактуального состояния. Интервал нужно останавливать в cleanup, а активный запрос отменять или игнорировать его результат.
Follow-up
Что могут спросить дальше
Короткие ответы на вопросы, которыми проверяют понимание Event Loop, микрозадач и таймеров в браузере.
Живые ответы
Видео с похожим вопросом
Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.
Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.
Что принимает метод reject в Promise 😎
Метод reject принимает одну причину отклонения Promise. На практике лучше передавать Error или свой класс ошибки, чтобы не потерять стек, причину сбоя и нормальную обработку в catch.
Что такое finally в Promise 😎
finally выполняет cleanup после завершения Promise, независимо от успеха или ошибки. Разбираем, как он влияет на цепочку, где помогает во фронтенде и какие ловушки есть с ошибками и загрузкой.