Таймеры в Node.js и за пределами

Модуль таймеров в Node.js содержит функции, которые выполняют код по истечении заданного периода времени. Таймеры не нужно импортировать через require(), так как все методы доступны глобально для эмуляции браузерного JavaScript API. Для того, чтобы полностью понять когда будут выполняться функции таймера, имеет смысл прочитать об Event Loop в Node.js.

Управление временным континуумом с Node.js

API Node.js предоставляет несколько способов планирования кода, который нужно выполнить, в какой-то момент в будущем. Приведенные ниже функции могут показатья знакомыми, так как они доступны в большинстве браузеров, но Node.js на самом деле предоставляет свою реализацию этих методов. Таймеры очень тесно интегрируются с системой, и, несмотря на то, что API Node.js отражает API браузера, все равно имеются некоторые различия в реализации.

"Когда я скажу" Выполнение ~ setTimeout()

setTimeout() может использоваться для планирования выполнения кода после назначенного количества миллисекунд. Эта функция аналогична window.setTimeout() из JavaScript API браузера, однако строка кода не может передаваться в качестве аргумента для выполнения.

setTimeout() первым параметром принимает функцию, которую нужно выполнить, и задержку в миллисекундах, как число, в качестве второго параметра. Также можно перечислить дополнительные аргументы и они будут переданы функции. Вот пример этого:

function myFunc(arg) {
  console.log(`arg was => ${arg}`);
}
 
setTimeout(myFunc, 1500, 'funky');

Функция myFunc() выполнится через время, максимально приближенное к 1500 миллисекундам (или 1.5 секунды), из-за вызова setTimeout().

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

setTimeout() возвращает объект Timeout, который можно использовать в качестве ссылки на тайм-аут, который был установлен. Этот объект можно использовать для отмены тайм-аута (см. clearTimeout() ниже), а также для изменения поведения при выполнении (см. unref() ниже).

"Сразу после этого" Выполнение ~ setImmediate()

setImmediate() выполнит код в конце текущего цикла событий. Этот код будет выполняться после любых операций ввода-вывода в текущем цикле событий и перед любым запланированными таймерами для следующего цикла событий. Такое выполнение кода можно рассматривать как "сразу после этого", то есть любой код, следующий за вызовом функции setImmediate(), будет выполняться до аргумента функции setImmediate().

Первым аргументом setImmediate() будет функция, которую нужно выполнить. Все последующие аргументы будут переданы функции при ее выполнении. Вот пример:

console.log('before immediate');
 
setImmediate((arg) => {
  console.log(`executing immediate: ${arg}`);
}, 'so immediate');
 
console.log('after immediate');

Функция, переданная в setImmediate(), будет выполнена после того, как будет выполнен весь исполняемый код, и в консоли мы увидим следующее:

before immediate
after immediate
executing immediate: so immediate

setImmediate() возвращает объект Immediate, который можно использовать для отмены запланированного immediate (см. clearImmediate() ниже).

Примечание: Не путайте setImmediate() и process.nextTick(). Между ними есть несколько основных различий. Во-первых, process.nextTick() выполнится перед любыми Immediate, а также перед любыми запланированными операциями ввода/вывода. Во-вторых, process.nextTick() не подлежит отмене, имеется в виду, что после того как вы запланировали выполнение кода с помощью process.nextTick(), то его выполнение не может быть приостановлено, также как и с обычной функцией. Обратитесь к этому руководству, чтобы лучше понять работу process.nextTick().

"Бесконечный цикл" Выполнение ~ setInterval()

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

function intervalFunc() {
  console.log('Cant stop me now!');
}
 
setInterval(intervalFunc, 1500);

В примере выше intervalFunc() будет выполняться каждые 1500 миллисекунд или 1.5 секунд, до тех пор, пока ее не остановят (см. ниже).

setInterval(), также как и setTimeout() возвращает объект Timeout, который можно использовать в качестве ссылки для изменения установленного интервала.

Отмена грядущего

Что нужно сделать, чтобы отменить Timeout или Immediate? setTimeout(), setImmediate() и setInterval() возвращают объект таймера, который можно использовать в качестве ссылки на установленные Timeout и Immediate объекты. При передаче этого объекта в соответствующую функцию clear - выполнение этого объекта будет полностью остановлено. clearTimeout(), clearImmediate() и clearInterval() - это те самые специальные функции. Давайте посмотрим на пример ниже:

const timeoutObj = setTimeout(() => {
  console.log('timeout beyond time');
}, 1500);
 
const immediateObj = setImmediate(() => {
  console.log('immediately executing immediate');
});
 
const intervalObj = setInterval(() => {
  console.log('interviewing the interval');
}, 500);
 
clearTimeout(timeoutObj);
clearImmediate(immediateObj);
clearInterval(intervalObj);

Оставляя тайм-ауты позади

Помните, что setTimeout и setInterval возвращают объект Timeout. У объекта Timeout есть два метода, чтобы расширить свое поведение, это unref() и ref(). Если есть объект Timeout, запланированный с использованием функции set, то для этого объекта может быть вызвана unref(). Это немного изменит поведение тем, что объект Timeout не выполнит запланированный код, если это последний код, который нужно выполнить. Объект Timeout не будет удерживать процесс в ожидании выполнения.

Аналогичным образом, объект Timeout, на котором был вызван unref(), может убрать это поведение, вызвав ref() на том же Timeout объекте, что затем обеспечит его выполнение. Однако имейте в виду, что это не точно восстанавливает исходное поведение по причинам производительности. Давайте взглянем на примеры ниже:

const timerObj = setTimeout(() => {
  console.log('will i run?');
});
 
// если оставить без внимания, то это выражение
// не позволит выполниться тайм-ауту выше, так как сам тайм-аут
// будет единственным, что не позволит программе завершиться
timerObj.unref();
 
// мы можем вернуть его к жизни вызвав ref() внутри immediate
setImmediate(() => {
  timerObj.ref();
});

Далее по событийному циклу

В событийном цикле и таймерах можно найти гораздо больше информации, чем в этом руководстве. Чтобы узнать больше о внутренностях цикла событий Node.js и о том, как работают таймеры во время выполнения - взгляните на это руководство: The Node.js Event Loop, Timers, and process.nextTick().

Вверх