Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions doc/api/timers.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,10 @@ Node.js makes no guarantees about the exact timing of when callbacks will fire,
nor of their ordering. The callback will be called as close as possible to the
time specified.

When `delay` is larger than `2147483647` or less than `1` or `NaN`, the `delay`
will be set to `1`. Non-integer delays are truncated to an integer.
When `delay` is larger than `2147483647`, a negative number, or `NaN`, the
`delay` will be set to `1`. A delay of `0` (or a positive sub-millisecond
value, which is truncated to `0`) schedules the callback as soon as possible.
Non-integer delays are truncated to an integer.

If `callback` is not a function, a [`TypeError`][] will be thrown.

Expand Down
10 changes: 9 additions & 1 deletion lib/internal/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,15 @@ class Timeout {
'\nTimeout duration was set to 1.',
'TimeoutNaNWarning');
}
after = 1; // Schedule on next tick, follows browser behavior

// setTimeout() accepts a delay of 0 or a positive sub-millisecond
// delay, which is truncated to 0 by insert() and thus scheduled as
// soon as possible, matching browsers. Every other invalid delay (and
// every setInterval() delay below 1 ms, so it does not fire as fast as
// the event loop allows) is still clamped to 1 ms.
if (isRepeat || after < 0 || NumberIsNaN(after) || after > TIMEOUT_MAX) {
after = 1; // Schedule on next tick, follows browser behavior
}
}

this._idleTimeout = after;
Expand Down
25 changes: 25 additions & 0 deletions test/parallel/test-timers-zero-delay-ordering.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';
const common = require('../common');
const assert = require('assert');

// setTimeout with a delay of 0 should schedule the callback as soon as
// possible, so that it runs before a timer scheduled with a 1 ms delay.
// See https://github.com/nodejs/node/issues/46596

const order = [];

setTimeout(common.mustCall(() => order.push('one')), 1);
setTimeout(common.mustCall(() => order.push('zero')), 0);

setTimeout(common.mustCall(() => {
assert.deepStrictEqual(order, ['zero', 'one']);

Check failure on line 15 in test/parallel/test-timers-zero-delay-ordering.js

View workflow job for this annotation

GitHub Actions / aarch64-linux: with shared openssl-3.6.3 / build

--- stderr --- node:internal/assert/utils:146 throw error; ^ AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + actual - expected [ + 'one', 'zero', - 'one' ] at Timeout.<anonymous> (/home/runner/work/_temp/node-v27.0.0-nightly2026-09-213470c19435-slim/test/parallel/test-timers-zero-delay-ordering.js:15:10) at Timeout._onTimeout (/home/runner/work/_temp/node-v27.0.0-nightly2026-09-213470c19435-slim/test/common/index.js:512:15) at listOnTimeout (node:internal/timers:693:17) at process.processTimers (node:internal/timers:626:7) { generatedMessage: true, code: 'ERR_ASSERTION', actual: [ 'one', 'zero' ], expected: [ 'zero', 'one' ], operator: 'deepStrictEqual', diff: 'simple' } Node.js v27.0.0-pre Command: out/Release/node /home/runner/work/_temp/node-v27.0.0-nightly2026-09-213470c19435-slim/test/parallel/test-timers-zero-delay-ordering.js

Check failure on line 15 in test/parallel/test-timers-zero-delay-ordering.js

View workflow job for this annotation

GitHub Actions / aarch64-linux: with shared openssl-3.5.8 / build

--- stderr --- node:internal/assert/utils:146 throw error; ^ AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + actual - expected [ + 'one', 'zero', - 'one' ] at Timeout.<anonymous> (/home/runner/work/_temp/node-v27.0.0-nightly2026-09-213470c19435-slim/test/parallel/test-timers-zero-delay-ordering.js:15:10) at Timeout._onTimeout (/home/runner/work/_temp/node-v27.0.0-nightly2026-09-213470c19435-slim/test/common/index.js:512:15) at listOnTimeout (node:internal/timers:693:17) at process.processTimers (node:internal/timers:626:7) { generatedMessage: true, code: 'ERR_ASSERTION', actual: [ 'one', 'zero' ], expected: [ 'zero', 'one' ], operator: 'deepStrictEqual', diff: 'simple' } Node.js v27.0.0-pre Command: out/Release/node /home/runner/work/_temp/node-v27.0.0-nightly2026-09-213470c19435-slim/test/parallel/test-timers-zero-delay-ordering.js

Check failure on line 15 in test/parallel/test-timers-zero-delay-ordering.js

View workflow job for this annotation

GitHub Actions / aarch64-linux: with shared openssl-4.0.2 / build

--- stderr --- node:internal/assert/utils:146 throw error; ^ AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + actual - expected [ + 'one', 'zero', - 'one' ] at Timeout.<anonymous> (/home/runner/work/_temp/node-v27.0.0-nightly2026-09-213470c19435-slim/test/parallel/test-timers-zero-delay-ordering.js:15:10) at Timeout._onTimeout (/home/runner/work/_temp/node-v27.0.0-nightly2026-09-213470c19435-slim/test/common/index.js:512:15) at listOnTimeout (node:internal/timers:693:17) at process.processTimers (node:internal/timers:626:7) { generatedMessage: true, code: 'ERR_ASSERTION', actual: [ 'one', 'zero' ], expected: [ 'zero', 'one' ], operator: 'deepStrictEqual', diff: 'simple' } Node.js v27.0.0-pre Command: out/Release/node /home/runner/work/_temp/node-v27.0.0-nightly2026-09-213470c19435-slim/test/parallel/test-timers-zero-delay-ordering.js
}), 2);

// A zero-millisecond delay must still be allowed for the promisified variant.
let resolved;
const p = require('node:timers/promises').setTimeout(0);
p.then(common.mustCall(() => { resolved = true; }));

setTimeout(common.mustCall(() => {
assert.strictEqual(resolved, true);
}), 2);
4 changes: 2 additions & 2 deletions test/parallel/test-timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ inputs.forEach((value, index) => {
}, value);
});

// All values in inputs array coerce to 1 ms. Therefore, they should all run
// before a timer set here for 2 ms.
// All values in inputs array coerce to a short delay (0 ms or 1 ms).
// Therefore, they should all run before a timer set here for 2 ms.

setTimeout(common.mustCall(() => {
// Assert that all other timers have run
Expand Down
Loading