setTimeout и колбэки
Чему вы научитесь:
- предсказывать порядок вывода в коде с
setTimeout, промисами иawait; - создавать промис и получать его значение через
then,catchиawait; - объяснять, почему колбэк
thenвыполняется раньше колбэка таймера; - ждать несколько промисов сразу через
Promise.allи ловить отказ.

Представьте повара, который один работает на кухне. Он ставит пирог в духовку, заводит кухонный таймер и не стоит у плиты сорок минут. Пока пирог печётся, повар режет салат и моет посуду. Когда таймер звенит, повар заканчивает начатое дело и только потом достаёт пирог.
Программе тоже часто приходится ждать: ответа сервера, нажатия кнопки, конца паузы. JavaScript на таком ожидании не останавливается. Он запоминает функцию, которую нужно вызвать позже, и сразу идёт дальше. Давайте посмотрим, что выведет программа:
console.log("start");
setTimeout(() => {
console.log("one second later");
}, 1000);
console.log("end");
// start
// end
// one second laterФункция setTimeout принимает функцию и задержку в миллисекундах. Сама она ничего не ждёт: запоминает функцию и отдаёт управление следующей строке. Поэтому end печатается сразу после start, а one second later появляется через секунду.
setTimeout и есть кухонный таймер: его завели, и программа занялась другим. Функция внутри — дело, которое нужно сделать, когда таймер зазвенит.
Колбэк вызывается позже
Как вы уже знаете из темы о методах массивов, колбэк — это функция, которую передают аргументом, чтобы её вызвали потом. Важно, когда именно её вызовут:
- колбэк
forEachвызывается сразу, по разу на элемент, и следующая строка ждёт последнего вызова; - колбэк
setTimeoutвызывается позже, когда остальная программа уже ушла вперёд.
Код, часть которого выполняется не на своём месте в файле, а позже, называют асинхронным. Порядок вывода в нём уже не совпадает с порядком строк. Читать такой код нужно не сверху вниз, а по времени: что выполнится сейчас, а что потом.
Колбэк при этом остаётся обычной функцией. Он видит переменные снаружи, как любое замыкание, и может их менять.
Задержка 0
Даже задержка 0 не значит «сразу»:
setTimeout(() => {
console.log("timer");
}, 0);
console.log("after");
// after
// timerВспомните повара: он не бросает начатое блюдо, даже если таймер уже звенит. JavaScript так же выполняет код по одному куску за раз. Пока работает текущий код, никакой колбэк не начнётся.
Колбэки, чьё время пришло, ждут в очереди, как записки с заказами на крючке. Событийный цикл — часть браузера или Node.js, которая берёт из очереди следующий колбэк, когда текущий код закончился.
Разберём пример по шагам:
- Текущий код заводит таймер с задержкой
0и сразу идёт дальше. - Строка
console.log("after")печатаетafter, и текущий код заканчивается. - Колбэк таймера уже ждёт в очереди: событийный цикл берёт его, и колбэк печатает
timer.
Поэтому timer печатается после after, хотя задержка нулевая. Этот вопрос часто задают на собеседовании: что выведет код с setTimeout(…, 0)? Ответ: сначала весь текущий код, потом колбэк таймера.
Задержка — это наименьшее время ожидания, а не точное. Если текущий код работает дольше, колбэк ждёт его конца.
Здесь аналогия с поваром перестаёт работать. Настоящий повар может отложить нож на полпути, а JavaScript не прерывает текущий код ни на миг: колбэк начнётся, только когда этот код дойдёт до конца.
Node.js не завершает программу, пока ждёт хотя бы один таймер, поэтому колбэк напечатает своё и после последней строки файла. В браузере страница работает, пока открыта, и таймер там тоже сработает.
Частая ошибка
Результат, который появится в колбэке, читают сразу после setTimeout:
let user;
setTimeout(() => {
user = { name: "Ann" };
}, 0);
console.log(user.name); // ✗TypeError: Cannot read properties of undefined (reading 'name')Последняя строка выполняется раньше колбэка, и в переменной user ещё undefined. Всё, что нужно сделать с результатом, пишут внутри колбэка: перенесите console.log(user.name) туда, после присваивания.
Главное
- Колбэк
setTimeoutвызывается позже, а следующая строка выполняется сразу. - Код, часть которого выполняется позже своего места в файле, называют асинхронным.
- Событийный цикл берёт колбэк из очереди, только когда текущий код закончился, даже при задержке
0. - Результат, который появляется в колбэке, используют внутри колбэка.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.