Node’s Timer Functions
Timer functions are higher-order functions that can be used to delay or repeat the execution of other functions (which they receive as their first argument).
Here’s an example about delaying:
// example1.js setTimeout( () => { console.log('Hello after 4 seconds'); }, 4 * 1000 );
This example uses setTimeout
to delay the printing of the greeting message by 4 seconds. The second argument to setTimeout
is the delay (in ms). This is why I multiplied 4 by 1000 to make it into 4 seconds.
The first argument to setTimeout
is the function whose execution will be delayed.
If you execute the example1.js
file with the node command, Node will pause for 4 seconds and then it’ll print the greeting message (and exit after that).
Note that the first argument to setTimeout
is just a function reference. It does not have to be an inline function like what example1.js
has. Here’s the same example without using an inline function:
const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000);
Passing Arguments
If the function that uses setTimeout
to delay its execution accepts any arguments, we can use the remaining arguments for setTimeout
itself (after the 2 we learned about so far) to relay the argument values to the delayed function.
// For: func(arg1, arg2, arg3, ...) // We can use: setTimeout(func, delay, arg1, arg2, arg3, ...)
Here’s an example:
// example2.js const rocks = who => { console.log(who + ' rocks'); }; setTimeout(rocks, 2 * 1000, 'Node.js');
The rocks
function above, which is delayed by 2 seconds, accepts a who
argument and the setTimeout
call relays the value "Node.js" as that who
argument.
Executing example2.js
with the node command will print out "Node.js rocks" after 2 seconds.
Note that timer functions like setTimeout are also available in browsers. However, the implementation of them in browsers is different than in Node. The argument values example above might not work in all browsers. You can accomplish the same functionality above without relying on timer arguments using JavaScript closures. If you’re not familiar with closures, check this short interactive course about them: https://jscomplete.com/labs/what-are-closures-in-javascript |
Repeating the execution of a function
What if I asked you to print a message every 4 seconds, forever?
While you can put setTimeout
in a loop, the Node timers API offers the setInterval
function as well, which would accomplish the requirement of doing something forever.
Here’s an example of setInterval:
// example3.js setInterval( () => console.log('Hello every 3 seconds'), 3000 );
This example will print its message every 3 seconds. Executing example3.js
with the node
command will make Node print this message forever, until you kill the process (with CTRL+C).
Cancelling Timers
Because calling a timer function schedules an action, that action can also be cancelled before it gets executed.
A call to setTimeout
returns a timer “ID” and you can use that timer ID with a clearTimeout
call to cancel that timer. Here’s an example:
// example4.js const timerId = setTimeout( () => console.log('You will not see this one!'), 0 ); clearTimeout(timerId);
This simple timer is supposed to fire after 0
ms (making it immediate), but it will not fire at all because we are capturing the timerId
value and canceling it right after with a clearTimeout
call.
When we execute example4.js
with the node
command, Node will not print anything and the process will just exit.
By the way, in Node.js, there is another way to do setTimeout
with 0
ms. The Node timers API has another function called setImmediate
and it’s basically the same thing as a setTimeout
with a 0
ms but we don’t have to specify a delay there:
setImmediate( () => console.log('I am equivalent to setTimeout with 0 ms'), );
The setImmediate function is not available in all browsers. Don’t use it for front-end code.
|
Just like clearTimeout
, there is also a clearInterval
function, which does the same thing but for setInerval
calls. There is also a clearImmediate
call as well.
A timer delay is not guaranteed
In the previous example, did you notice how executing something with setTimeout
after 0
ms did not mean execute it right away (after the setTimeout
line), but rather execute it right away after everything else in the script (including the clearTimeout
call)?
Let me make this point clear with an example. Here’s a simple setTimeout
call that should fire after half a second, but it won’t:
// example5.js setTimeout( () => console.log('Hello after 0.5 seconds. MAYBE!'), 500, ); for (let i = 0; i < 1e10; i++) { // Block Things Synchronously }
Right after defining the timer in this example, we block the runtime synchronously with a big for
loop. The 1e10
is 1
with 10
zeros after it, so the loop is a `10 `Billion ticks loop (which basically simulates a busy CPU). Node can do nothing while this loop is ticking.
This is, of course, a very bad thing to do in practice, but it’ll help you here to understand that setTimeout
delay is not a guaranteed value, but rather a minimum value. The 500
ms means a minimum delay of 500
ms. In reality, the script might take a lot longer to print its greeting line. It might have to wait on a blocking loop to finish first.
Who exactly “calls” the delayed functions?
When you use the JavaScript this
keyword inside a regular function, like this:
function whoCalledMe() { console.log('Caller is', this); }
The value inside the this
keyword will represent the caller of the function. If you define the function above inside a Node REPL, the caller will be the global
object. If you define the function inside a browser’s console, the caller will be the window
object. The caller basically represents the environment in which the function was called.
Let’s define the function as a property on an object to make this a bit more clear:
const obj = { id: '42', whoCalledMe() { console.log('Caller is', this); } }; // The function reference is now: obj.whoCallMe
Now when you call the obj.whoCallMe
function using its reference directly, the caller will be the obj
object (identified by its id):
Now, the question is, what would the caller be if we pass the reference of obj.whoCallMe
to a setTimetout
call?
// What will this print?? setTimeout(obj.whoCalledMe, 0);
Who will the caller be in that case?
The answer is different based on where the timer function is executed. You simply can’t depend on who the caller is in that case. You lose control of the caller because the timer implementation will be the one invoking your function now. If you test it in a Node REPL, you’d get a Timetout
object as the caller:
Note that this only matters if you’re using JavaScript’s this
keyword inside regular functions. You don’t need to worry about the caller at all if you’re using arrow functions.