From ac153bd2a6f45e5dedebacab362135a82ac08dbd Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 20 Dec 2015 12:30:04 +0100 Subject: [PATCH] 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 Reviewed-By: Colin Ihrig Reviewed-By: Brian White Reviewed-By: Trevor Norris Reviewed-By: Rod Vagg --- doc/api/timers.markdown | 6 ++ lib/timers.js | 12 ++++ .../test-timers-throw-when-cb-not-function.js | 62 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 test/parallel/test-timers-throw-when-cb-not-function.js diff --git a/doc/api/timers.markdown b/doc/api/timers.markdown index cf2f24ae938adb..7e4efb47c12929 100644 --- a/doc/api/timers.markdown +++ b/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 until the next event loop iteration. +If `callback` is not a function `setImmediate()` will throw immediately. + ## setInterval(callback, delay[, arg][, ...]) 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 `delay`. +If `callback` is not a function `setInterval()` will throw immediately. + ## setTimeout(callback, delay[, arg][, ...]) 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 immediately, as if the `delay` was set to 1. +If `callback` is not a function `setTimeout()` will throw immediately. + ## unref() The opaque value returned by [`setTimeout`][] and [`setInterval`][] also has the diff --git a/lib/timers.js b/lib/timers.js index 24dc7e1c263c7e..66f77dfd0656df 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -177,6 +177,10 @@ exports.enroll = function(item, msecs) { 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 if (!(after >= 1 && after <= TIMEOUT_MAX)) { @@ -233,6 +237,10 @@ exports.clearTimeout = function(timer) { 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 if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) { @@ -419,6 +427,10 @@ Immediate.prototype._idlePrev = undefined; exports.setImmediate = function(callback, arg1, arg2, arg3) { + if (typeof callback !== 'function') { + throw new TypeError('"callback" argument must be a function'); + } + var i, args; var len = arguments.length; var immediate = new Immediate(); diff --git a/test/parallel/test-timers-throw-when-cb-not-function.js b/test/parallel/test-timers-throw-when-cb-not-function.js new file mode 100644 index 00000000000000..7397c23b4933c9 --- /dev/null +++ b/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/);