Skip to content

Commit 1ddf9b2

Browse files
committed
timers: allow setTimeout to accept a delay of 0
A setTimeout() delay of 0 (or a positive sub-millisecond delay, which is truncated to 0 by insert()) is now scheduled as soon as possible instead of being clamped to 1 ms, matching browser behavior. Negative delays, NaN, and values above TIMEOUT_MAX are still clamped to 1 ms, and setInterval() keeps clamping delays below 1 ms to 1 ms to avoid firing as fast as the event loop allows. Refs: #46596 Assisted-by: pi Signed-off-by: Matteo Collina <matteo.collina@gmail.com>
1 parent ebef774 commit 1ddf9b2

4 files changed

Lines changed: 40 additions & 5 deletions

File tree

doc/api/timers.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,10 @@ Node.js makes no guarantees about the exact timing of when callbacks will fire,
276276
nor of their ordering. The callback will be called as close as possible to the
277277
time specified.
278278

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

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

lib/internal/timers.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,15 @@ class Timeout {
239239
'\nTimeout duration was set to 1.',
240240
'TimeoutNaNWarning');
241241
}
242-
after = 1; // Schedule on next tick, follows browser behavior
242+
243+
// setTimeout() accepts a delay of 0 or a positive sub-millisecond
244+
// delay, which is truncated to 0 by insert() and thus scheduled as
245+
// soon as possible, matching browsers. Every other invalid delay (and
246+
// every setInterval() delay below 1 ms, so it does not fire as fast as
247+
// the event loop allows) is still clamped to 1 ms.
248+
if (isRepeat || after < 0 || NumberIsNaN(after) || after > TIMEOUT_MAX) {
249+
after = 1; // Schedule on next tick, follows browser behavior
250+
}
243251
}
244252

245253
this._idleTimeout = after;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
5+
// setTimeout with a delay of 0 should schedule the callback as soon as
6+
// possible, so that it runs before a timer scheduled with a 1 ms delay.
7+
// See https://github.com/nodejs/node/issues/46596
8+
9+
const order = [];
10+
11+
setTimeout(common.mustCall(() => order.push('one')), 1);
12+
setTimeout(common.mustCall(() => order.push('zero')), 0);
13+
14+
setTimeout(common.mustCall(() => {
15+
assert.deepStrictEqual(order, ['zero', 'one']);
16+
}), 2);
17+
18+
// A zero-millisecond delay must still be allowed for the promisified variant.
19+
let resolved;
20+
const p = require('node:timers/promises').setTimeout(0);
21+
p.then(() => { resolved = true; });
22+
23+
setTimeout(common.mustCall(() => {
24+
assert.strictEqual(resolved, true);
25+
}), 2);

test/parallel/test-timers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ inputs.forEach((value, index) => {
6565
}, value);
6666
});
6767

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

7171
setTimeout(common.mustCall(() => {
7272
// Assert that all other timers have run

0 commit comments

Comments
 (0)