Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Таймеры

Отдельное место в асинхронном мире занимают таймеры. Они позволяют отложить выполнение какой-либо функции "на потом". Наиболее важная функция для работы с таймерами — setTimeout(f, delay)

const f = () => console.log('hey!');
setTimeout(f, 1000);

В коде выше функция f выполнится не раньше, чем через секунду. Об этом нам говорит второй параметр, в который передаётся время, указанное в миллисекундах, после которого запустится функция, указанная первым параметром. По историческим причинам у таймеров есть минимальная задержка, которую они соблюдают всегда, и она равна четырём миллисекундам. Другими словами, нет разницы между вызовами setTimeout(f, 1), setTimeout(f, 3) и setTimeout(f, 4) — во всех этих случаях минимальная задержка равна 4.

Для чего нужны таймеры? У них много разных применений. Если говорить про браузер, то это могут быть автоматически скрываемые элементы: например, нотификации. Другой пример — это регулярный (например, раз в 5 секунд) Ajax-запрос для получения новых данных. На сервере таймеры используются реже, но тоже встречаются: с помощью них можно разбить объёмную синхронную операцию на несколько кусков, давая возможность выполниться другому коду.

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

Функция, переданная в таймер, выполняется не в текущем стеке вызовов, а значит к таймерам применимы все те особенности и подходы, о которых мы говорили ранее. Ошибки, возникающие в таймерах, невозможно отследить с помощью try/catch, для этого нужно использовать колбеки.

const f = () => console.log('hey!');
console.log('before timeout');
setTimeout(f, 1000);
console.log('after timeout');
// скрипт не заканчивается, а дожидается выполнения таймеров

Запуск:

$ node index.js

before timeout
after timeout
hey!

Попробуйте ответить на такой вопрос. Могут ли таймеры гарантировать точный запуск через указанный промежуток времени? На самом деле не могут. Все зависит от того, что выполняется прямо сейчас. Проверкой таймеров занимается рантайм в тот момент, когда в текущем стеке вызовов не осталось кода. Если запустить тяжелое вычисление, которое не прекращается долго, то все колбеки, все таймеры, будут ждать пока вычисление закончится. Фактически это означает, что в таймерах задается минимальное время, после которого их можно запускать.

Эта особенность имеет два важных следствия:

  • Старайтесь минимизировать время выполнения долгих вычислений. Например, их можно разбивать на шаги.
  • Не рассчитывайте на точность времени вызова. Оно всегда будет отличаться в большую сторону.

Таймеры можно не только создавать, но и отменять. Вызов setTimeout возвращает специальное значение — идентификатор таймера. Если передать его в функцию clearTimeout, то таймер отменится:

const f = () => console.log('hey!');
console.log('before timeout');
// В браузере идентификатор таймера это числовое значение
// В node.js это объект
const timerId = setTimeout(f, 1000);
console.log('after timeout');
clearTimeout(timerId);

Запуск:

$ node index.js

before timeout
after timeout

То, что асинхронная функция вернула идентификатор таймера, не противоречит тому, что мы ранее говорили про возврат в асинхронных функциях. Асинхронная функция не может вернуть результат выполнения асинхронной операции, но может вернуть что-нибудь другое, что делалось синхронно внутри нее. В случае таймеров, идентификатор таймера возвращается синхронно, а вот сам таймер выполняется асинхронно.

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

const f = (message) => console.log(message);
console.log('before timeout');
setTimeout(f('hey!'), 1000);
console.log('after timeout');

Запуск:

$ node index.js

before timeout
hey!
timers.js:390
    throw new ERR_INVALID_CALLBACK();
    ^

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function

До последнего лога дело не дошло, потому что скрипт упал на вызове setTimeout, так как он ожидал на вход функцию, а пришла не функция (вызов в примере вернул значение undefined).

Передать данные внутрь функции можно тремя способами:

Дополнительные параметры в setTimeout

Все аргументы, переданные в setTimeout после второго аргумента (времени), автоматически становятся аргументами функции, которую вызовет таймер.

const f = (a, b) => console.log(a + b);
setTimeout(f, 1000, 5, 8);
// =>  13

Функция-обёртка

Наиболее распространённый способ — создание функции-обёртки. Такой способ лучше предыдущего из-за его прозрачности: сразу видно, что происходит.

const f = (a, b) => console.log(a + b);
setTimeout(() => f(5, 8), 1000);
// =>  13

bind

Последний способ — использовать функцию bind. Основное предназначение этой функции — смена контекста функции. Но как побочный эффект она может использоваться для частичного применения:

const f = (a, b) => console.log(a + b);
// Первый параметр null потому что контекст не меняется
setTimeout(f.bind(null, 5, 8), 1000);
// =>  13

Вызов этой функции возвращает новую функцию с применёнными аргументами.

Важно понимать, что таймер не делает операцию (та, что выполняется при вызове функции, переданной в setTimeout) асинхронной — таймер лишь откладывает время её выполнения. Если сама операция синхронная, то после запуска она заблокирует основной поток выполнения программы, и все остальные будут ждать её завершения.

setInterval

Функция setInterval имеет точно такую же сигнатуру, как и setTimeout. Смысл аргументов — тот же самый. Разница в том, что setInterval автоматически запускает функцию не один раз, а до тех пор, пока её явно не остановят через clearInterval. Время между запусками равно переданному второму параметру.

const id = setInterval(() => console.log(new Date()), 5000);
setTimeout(() => clearInterval(id), 16000);

// $ node index.js
// 2019-06-05T19:05:28.149Z
// 2019-06-05T19:05:33.172Z
// 2019-06-05T19:05:38.177Z

Таймер можно остановить изнутри, передав в колбек его id.

let counter = 0;
const id = setInterval(() => {
  counter += 1;
  if (counter === 4) {
    clearInterval(id);
    return;
  }
  console.log(new Date());
}, 5000);

Дополнительные материалы

  1. setTimeout
  2. setInterval

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Для полного доступа к курсу, нужна профессиональная подписка

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

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

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

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

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