Иногда нам нужно что бы функции отрабатывали не сразу, а спустя определенное время или с заданным интервалом. В спецификации JavaScript не предусмотрено подобное поведение, но для таких случаев в большинстве сред исполнения JavaScript есть методы планирования setTimeout и setInterval о которых мы сегодня и поговорим.

Синтаксис и особенности

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

setTimeout(func, delay, …args);
setInterval(func, delay, …args);

Разница между ними в том, что setTimeout задает задержку до единовременного исполнения функции, а setInterval - интервал между многократными вызовами.

Планируемая функция может быть описана как внутри метода, так и объявлена в другой части кода. Для сохранения совместимости в браузере, функция может быть передана в виде строки кода и создана динамически. Например эти две строки кода в браузере будут равносильны:

setTimeout(console.log("Hello!"), 1000);
setTimeout('console.log("Hello!")', 1000);

Но делать так не стоит из соображений безопасности. К тому же, при исполнении такого кода в NodeJS вылетит ошибка.

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

const sayHi = () => {
  console.log("Hi!");
};
setTimeout(sayHi, 0);
setTimeout(sayHi, -1000);
setTimeout(sayHi);
// Во всех случаях задержка равно 0

Важно понимать что длительность задержки нельзя переопределить после того, как таймер запланирован. Это происходит потому что setTimeout уже был запланирован, благодаря работе Event Loop и значение задержки уже определено.

const sayHi = () => {
  console.log("Hi!");
};
let delay = 1000;
setTimeout(sayHi, delay);
delay = 2000;
// Таймер отработает чере 1 секунду

Как Вы уже могли заметить, отложенная функция не вызывается внутри setInterval и setTimeout, а только передается в них. Аргументы отложенной функции передаются как параметры самого таймера:

const greating = (name) => {console.log(`Hi, ${name}!`)};
setTimeout(greating, 1000, "John");

Отмена таймеров

Вызов setTimeout или setInterval возвращает id таймера. Передав этот идентификатор в функцию clearTimeout или clearInterval мы отменим срабатывание таймера. Id всех рассматриваемых таймеров хранятся в одном месте и обе функции отмены делают идентичную работу просто удаляя из общего хранилища таймер по идентификатору. Несмотря на это, желательно использовать для отмены setTimeout именно clearTimeout и clearInterval для setInterval, что бы повысить читаемость кода.

const sayHi = () => {console.log("Hi!");};
const id = setTimeout(sayHi, 2000);
clearInterval(id);
//таймер отменен, но при чтении не понятно что за интервал
clearTimeout(id);
//делает то же самое, но читается намного понятнее

Реальная длительность задержек

Передавая в setTimeout и setInterval нужное время задержки вызова функции, мы ожидаем что именно через такое количество миллисекунд вызов и произойдет, но на практике время задержки может отличаться, при чем довольно сильно. Вызвано такое поведение особенностями работы Цикла Событий (Event Loop) в браузерах и Node JS. Не будем вдаваться в подробности т.к. это тема для отдельной статьи. Для понимания причин отличия переданного значения задержки от реальных результатов работы кода нам достаточно знать, что вызов функции из таймера происходит только после завершения некоторых других операций, таких, как, например синхронные операции, события WebApi, Promise и т.п. Даже если задана задержка в 0 миллисекунд.

setTimeout(() => {
  console.log("Таймаут");
}, 0);
let myPromise = new Promise((resolve, reject) => {
  console.log("Создание промиса");
  resolve();
});
myPromise.then(() => {
  console.log("Обработка промиса");
});
console.log("Синхронный код");
//Порядок вывода в консоль будет таким:
//1: Создание промиса
//2: Синхронный код
//3: Обработка промиса
//4: Таймаут

При этом длительные операции могут сильно отсрочить срабатывание таймера, например, если это сложный расчет, сильно нагружающий процессор.

setTimeout(() => console.log('Таймер на 0.1с'), 100);
let i = 0;
for(let j = 0; j < 100000000; j++) {
  // сложная операция, занимающая больше 100 мс
  i++;
}
//В консоли наш текст появится спустя более чем 100мс

Комментарии (0)