Browse Source

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: https://github.com/nodejs/node/pull/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>
process-exit-stdio-flushing
Anna Henningsen 9 years ago
committed by Rod Vagg
parent
commit
ac153bd2a6
  1. 6
      doc/api/timers.markdown
  2. 12
      lib/timers.js
  3. 62
      test/parallel/test-timers-throw-when-cb-not-function.js

6
doc/api/timers.markdown

@ -37,6 +37,8 @@ The entire callback queue is processed every event loop iteration. If you queue
an immediate from inside an executing callback, that immediate won't fire an immediate from inside an executing callback, that immediate won't fire
until the next event loop iteration. until the next event loop iteration.
If `callback` is not a function `setImmediate()` will throw immediately.
## setInterval(callback, delay[, arg][, ...]) ## setInterval(callback, delay[, arg][, ...])
To schedule the repeated execution of `callback` every `delay` milliseconds. To schedule the repeated execution of `callback` every `delay` milliseconds.
@ -47,6 +49,8 @@ To follow browser behavior, when using delays larger than 2147483647
milliseconds (approximately 25 days) or less than 1, Node.js will use 1 as the milliseconds (approximately 25 days) or less than 1, Node.js will use 1 as the
`delay`. `delay`.
If `callback` is not a function `setInterval()` will throw immediately.
## setTimeout(callback, delay[, arg][, ...]) ## setTimeout(callback, delay[, arg][, ...])
To schedule execution of a one-time `callback` after `delay` milliseconds. 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
milliseconds (approximately 25 days) or less than 1, the timeout is executed milliseconds (approximately 25 days) or less than 1, the timeout is executed
immediately, as if the `delay` was set to 1. immediately, as if the `delay` was set to 1.
If `callback` is not a function `setTimeout()` will throw immediately.
## unref() ## unref()
The opaque value returned by [`setTimeout`][] and [`setInterval`][] also has the The opaque value returned by [`setTimeout`][] and [`setInterval`][] also has the

12
lib/timers.js

@ -177,6 +177,10 @@ exports.enroll = function(item, msecs) {
exports.setTimeout = function(callback, after) { exports.setTimeout = function(callback, after) {
if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function');
}
after *= 1; // coalesce to number or NaN after *= 1; // coalesce to number or NaN
if (!(after >= 1 && after <= TIMEOUT_MAX)) { if (!(after >= 1 && after <= TIMEOUT_MAX)) {
@ -233,6 +237,10 @@ exports.clearTimeout = function(timer) {
exports.setInterval = function(callback, repeat) { exports.setInterval = function(callback, repeat) {
if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function');
}
repeat *= 1; // coalesce to number or NaN repeat *= 1; // coalesce to number or NaN
if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) { if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
@ -419,6 +427,10 @@ Immediate.prototype._idlePrev = undefined;
exports.setImmediate = function(callback, arg1, arg2, arg3) { exports.setImmediate = function(callback, arg1, arg2, arg3) {
if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function');
}
var i, args; var i, args;
var len = arguments.length; var len = arguments.length;
var immediate = new Immediate(); var immediate = new Immediate();

62
test/parallel/test-timers-throw-when-cb-not-function.js

@ -0,0 +1,62 @@
'use strict';
const common = require('../common');
const assert = require('assert');
function doSetTimeout(callback, after) {
return function() {
setTimeout(callback, after);
};
}
assert.throws(doSetTimeout('foo'),
/"callback" argument must be a function/);
assert.throws(doSetTimeout({foo: 'bar'}),
/"callback" argument must be a function/);
assert.throws(doSetTimeout(),
/"callback" argument must be a function/);
assert.throws(doSetTimeout(undefined, 0),
/"callback" argument must be a function/);
assert.throws(doSetTimeout(null, 0),
/"callback" argument must be a function/);
assert.throws(doSetTimeout(false, 0),
/"callback" argument must be a function/);
function doSetInterval(callback, after) {
return function() {
setInterval(callback, after);
};
}
assert.throws(doSetInterval('foo'),
/"callback" argument must be a function/);
assert.throws(doSetInterval({foo: 'bar'}),
/"callback" argument must be a function/);
assert.throws(doSetInterval(),
/"callback" argument must be a function/);
assert.throws(doSetInterval(undefined, 0),
/"callback" argument must be a function/);
assert.throws(doSetInterval(null, 0),
/"callback" argument must be a function/);
assert.throws(doSetInterval(false, 0),
/"callback" argument must be a function/);
function doSetImmediate(callback, after) {
return function() {
setImmediate(callback, after);
};
}
assert.throws(doSetImmediate('foo'),
/"callback" argument must be a function/);
assert.throws(doSetImmediate({foo: 'bar'}),
/"callback" argument must be a function/);
assert.throws(doSetImmediate(),
/"callback" argument must be a function/);
assert.throws(doSetImmediate(undefined, 0),
/"callback" argument must be a function/);
assert.throws(doSetImmediate(null, 0),
/"callback" argument must be a function/);
assert.throws(doSetImmediate(false, 0),
/"callback" argument must be a function/);
Loading…
Cancel
Save