Skip to content

Commit ac153bd

Browse files
addaleaxrvagg
authored andcommitted
timers: fail early when callback is not a function
`setTimeout()`, `setInterval()` and `setIntermediate` currently throw errors when receiving non-function objects as their first argument, but only do so when trying to execute the callback, i.e. after the waited time has passed. This may complicate debugging when a lot of calls to `setTimeout()`/etc. are involved, so failing as early as possible seems like a good idea. `setTimeout()` historically ignored an falsy first argument, while the other functions do not and throw instead. This patch changes this behaviour to make all three match and adds remarks in the corresponding documentation. PR-URL: #4362 Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Brian White <mscdex@mscdex.net> Reviewed-By: Trevor Norris <trev.norris@gmail.com> Reviewed-By: Rod Vagg <rod@vagg.org>
1 parent 7c8efeb commit ac153bd

File tree

3 files changed

+80
-0
lines changed

3 files changed

+80
-0
lines changed

doc/api/timers.markdown

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ The entire callback queue is processed every event loop iteration. If you queue
3737
an immediate from inside an executing callback, that immediate won't fire
3838
until the next event loop iteration.
3939

40+
If `callback` is not a function `setImmediate()` will throw immediately.
41+
4042
## setInterval(callback, delay[, arg][, ...])
4143

4244
To schedule the repeated execution of `callback` every `delay` milliseconds.
@@ -47,6 +49,8 @@ To follow browser behavior, when using delays larger than 2147483647
4749
milliseconds (approximately 25 days) or less than 1, Node.js will use 1 as the
4850
`delay`.
4951

52+
If `callback` is not a function `setInterval()` will throw immediately.
53+
5054
## setTimeout(callback, delay[, arg][, ...])
5155

5256
To schedule execution of a one-time `callback` after `delay` milliseconds.
@@ -62,6 +66,8 @@ To follow browser behavior, when using delays larger than 2147483647
6266
milliseconds (approximately 25 days) or less than 1, the timeout is executed
6367
immediately, as if the `delay` was set to 1.
6468

69+
If `callback` is not a function `setTimeout()` will throw immediately.
70+
6571
## unref()
6672

6773
The opaque value returned by [`setTimeout`][] and [`setInterval`][] also has the

lib/timers.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ exports.enroll = function(item, msecs) {
177177

178178

179179
exports.setTimeout = function(callback, after) {
180+
if (typeof callback !== 'function') {
181+
throw new TypeError('"callback" argument must be a function');
182+
}
183+
180184
after *= 1; // coalesce to number or NaN
181185

182186
if (!(after >= 1 && after <= TIMEOUT_MAX)) {
@@ -233,6 +237,10 @@ exports.clearTimeout = function(timer) {
233237

234238

235239
exports.setInterval = function(callback, repeat) {
240+
if (typeof callback !== 'function') {
241+
throw new TypeError('"callback" argument must be a function');
242+
}
243+
236244
repeat *= 1; // coalesce to number or NaN
237245

238246
if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
@@ -419,6 +427,10 @@ Immediate.prototype._idlePrev = undefined;
419427

420428

421429
exports.setImmediate = function(callback, arg1, arg2, arg3) {
430+
if (typeof callback !== 'function') {
431+
throw new TypeError('"callback" argument must be a function');
432+
}
433+
422434
var i, args;
423435
var len = arguments.length;
424436
var immediate = new Immediate();
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
5+
function doSetTimeout(callback, after) {
6+
return function() {
7+
setTimeout(callback, after);
8+
};
9+
}
10+
11+
assert.throws(doSetTimeout('foo'),
12+
/"callback" argument must be a function/);
13+
assert.throws(doSetTimeout({foo: 'bar'}),
14+
/"callback" argument must be a function/);
15+
assert.throws(doSetTimeout(),
16+
/"callback" argument must be a function/);
17+
assert.throws(doSetTimeout(undefined, 0),
18+
/"callback" argument must be a function/);
19+
assert.throws(doSetTimeout(null, 0),
20+
/"callback" argument must be a function/);
21+
assert.throws(doSetTimeout(false, 0),
22+
/"callback" argument must be a function/);
23+
24+
25+
function doSetInterval(callback, after) {
26+
return function() {
27+
setInterval(callback, after);
28+
};
29+
}
30+
31+
assert.throws(doSetInterval('foo'),
32+
/"callback" argument must be a function/);
33+
assert.throws(doSetInterval({foo: 'bar'}),
34+
/"callback" argument must be a function/);
35+
assert.throws(doSetInterval(),
36+
/"callback" argument must be a function/);
37+
assert.throws(doSetInterval(undefined, 0),
38+
/"callback" argument must be a function/);
39+
assert.throws(doSetInterval(null, 0),
40+
/"callback" argument must be a function/);
41+
assert.throws(doSetInterval(false, 0),
42+
/"callback" argument must be a function/);
43+
44+
45+
function doSetImmediate(callback, after) {
46+
return function() {
47+
setImmediate(callback, after);
48+
};
49+
}
50+
51+
assert.throws(doSetImmediate('foo'),
52+
/"callback" argument must be a function/);
53+
assert.throws(doSetImmediate({foo: 'bar'}),
54+
/"callback" argument must be a function/);
55+
assert.throws(doSetImmediate(),
56+
/"callback" argument must be a function/);
57+
assert.throws(doSetImmediate(undefined, 0),
58+
/"callback" argument must be a function/);
59+
assert.throws(doSetImmediate(null, 0),
60+
/"callback" argument must be a function/);
61+
assert.throws(doSetImmediate(false, 0),
62+
/"callback" argument must be a function/);

0 commit comments

Comments
 (0)