Таймеры в 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().