Pascal Akunne A JavaScript developer focused on building human-centric products with HTML, CSS, React, Node.js, and MongoDB

Using setTimeout() and other timer APIs in Node.js

7 min read 2013

Node js SetTimeout Timer Function

Editor’s note: This article was last updated on 21 June 2023 to include additional information about Node.js timers, like error handling and throttling API requests. 

Node.js has various built-in methods that make managing asynchronous tasks easier compared to other server-side programming languages. The event loop implements the asynchronous functionality of Node.js; it is a single-threaded, semi-infinite loop that executes I/O or timer callbacks sequentially using a set of queues.

The event loop has six different phases: timers, pending callbacks, idle or prepare, poll, check, and close callbacks. In this article, we’ll focus on the timers phase. We’ll review examples of setTimeout(), clearTimeout(), and a few other Node.js timer functions. Let’s get started!

Jump ahead:

Setting Node.js timer functions

The Node.js timer module contains functions that run code after a set amount of time. Timer methods are globally available to imitate the browser JavaScript API, and therefore, they do not need to be imported via require().

setTimeout()

We can use setTimeout() to set code execution after a certain period of time has passed in milliseconds. It is similar to the JavaScript API’s window.setTimeout() function in browsers, however, you can’t pass a string of code to be executed. We’ll cover this in more detail later.

The setTimeout() function takes three arguments:

  • First: The function to be run
  • Second: The delay in milliseconds
  • Third: An optional argument to be passed to the function

Below is an example of the setTimeout() function:

const myTimeout = setTimeout(myGreeting, 5000, "Pascal");
function myGreeting(arg) {
  console.log("Welcome back " + arg)
}

Because of the setTimeout call, the myGreeting() function in the example above will execute after close to 5000 milliseconds, or five seconds. The execution of the timeout can be slightly delayed due to other executing codes that run in the event loop. The third parameter, Pascal, is a string that is passed to the myGreeting() function as its argument.

setTimeout() returns a Timeout object, which can be used to terminate the timeout using the clear method, known as clearTimeout(). One real life use case of a setTimeout() function is a countdown to a flash sale in an ecommerce app.

setInterval()

setInterval() causes an indefinite loop to be executed. It is most useful for repeating a piece of code with a millisecond delay. SetInterval(), like setTimeout(), requires three arguments:

  • First: The function to be executed
  • Second: The delay in milliseconds
  • Third: An optional argument to be passed to the function

The code below shows an example of the setInterval() function:

function myInterval() {
  console.log('Catch me if you can!');
}
setInterval(myInterval, 3000);

In the example above, the myInterval() function will run every 3000 milliseconds, or three seconds, until it is stopped. Similar to setTimeout(), the delay time may not be exact owing to other code operations that may occupy the event loop.

setInterval() produces a Timeout object that you can use to reference and adjust the previously set interval. One real life use case of the setInterval() function is fetching the latest price of Ethereum at an interval of 60 seconds in a crypto trading app.

setImmediate()

The setImmediate() function runs code after the current event loop cycle is completed. setImmediate() is similar to setTimeout() with a 0ms time delay. To put it another way, the setImmediate() method will execute code after each statement in the script is executed.

setImmediate() takes two arguments:

  • First: The function to be executed
  • Second: The function’s parameter (optional)

Below is an example of the setImmediate() function:

console.log('before setImmediate');

const myImmediate = setImmediate((arg) => {
  console.log(`Hello and welcome ${arg}!`);
}, 'Pascal');
console.log('After setImmediate');

for(let i=0; i<10; i++){
  console.log(i);
}

In the example above, the setImmediate() function will run after all executable code has completed. The output will be as follows:

before immediate
after immediate
0
1
2
3
4
5
6
7
8
9
Hello and welcome Pascal!

If you want to make a long, CPU-bound process async so that the event loop will continue to operate and handle other requests, setImmediate() is a decent option.

Clearing Node.js timer functions

setTimeout(), setInterval(), and setImmediate() all return an object that can be used to refer to the Timeout or Immediate objects, respectively. Node.js provides clear functions for stopping or cancelling the execution of the Timeout or Immediate objects.

clearTimeout()

The clearTimeout() function is used to cancel the Timeout object created by the setTimeout() function:

const myTimeout = setTimeout(myGreeting, 5000, "Pascal");
function myGreeting(arg) {
  console.log("Welcome back " + arg)
}

clearTimeout(myTimeout)

clearInterval()

The clearInterval() function halts the Interval object created by the setInterval() function:

function myInterval() {
  console.log('Catch me if you can!');
}
setInterval(myInterval, 3000);

clearInterval(myInterval)

clearImmediate()

The clearImmediate() function stops the Immediate objected returned by the setImmediate() function:

console.log('before setImmediate');

const myImmediate = setImmediate((arg) => {
  console.log(`Hello and welcome ${arg}!`);
}, 'Pascal');
console.log('After setImmediate');

for(let i=0; i<10; i++){
  console.log(i);
}

clearImmediate(myImmediate);

Using window.setTimeout() vs. arrow functions with setTimeout()

The scope and context of the callback function differ between using window.setTimeout() and arrow functions with setTimeout(). In web browsers, the window.setTimeout() method is generally used to schedule a function to be run after a given delay. It works inside the context of the global window object. On the other hand, when using arrow functions with setTimeout(), the context of the arrow function is decided lexically. This signifies that the context within the arrow function is inherited from its scope.

By default, the callback function provided to window.setTimeout() is not restricted to any particular context. If you use this within a callback function, it will usually refer to the global window object. Arrow functions inherit the lexical this from the scope in which they are defined. As a result, if you use an arrow function as the callback in setTimeout(), the value of this will be the same as in the surrounding scope.

Using setTimeout() recursively

Using setTimeout() recursively is a JavaScript technique that allows you to schedule a function to execute repeatedly at a specified interval. By calling setTimeout() instead of setInterval() from within the callback function itself, you can create a recursive loop until a certain condition is met or an exit condition is triggered. The recursive approach to setTimeout() means that the tasks can fully control when the delay starts.

Consider the following example:

function countDown(counter) {
  console.log(counter);

  if (counter === 0) {
    console.log('Count down completed.');
    return;
  }
  setTimeout(() => {
    countDown(counter - 1);
  }, 1000);
}

countDown(10);

In the code above, we define a countDown() callback function and pass a counter argument. Within the function, the current counter is logged to the console, followed by a condition to see if the counter equals zero.

If the condition is true, a statement is logged and the code stops running. If the condition is false, the setTimeout() function is called, and the countDown() is run every 1000 milliseconds or one second. Here, the countDown() function takes in the current counter and subtracts one from it.



Other Node.js timer functions

unref()

The unref() timeout method notifies the Timeout object that the Node.js event loop is no longer required. Because there is no other event keeping the event loop going in this situation, it will end before the callback to setTimeout() is executed:

const myTimeout = setTimeout(myGreeting, 5000, "Pascal");
function myGreeting(arg) {
  console.log("Welcome back " + arg)
}

// Checking the myTimeout.hasRef() object
console.log(myTimeout.hasRef());

// Unreferencing
console.log(myTimeout.unref());

For the example above, the output will be as follows:

true
Timeout {
  _idleTimeout: 5000,
  _idlePrev: [TimersList],
  _idleNext: [TimersList],
  _idleStart: 68,
  _onTimeout: [Function: myGreeting],
  _timerArgs: [Array],
  _repeat: null,
  _destroyed: false,
  [Symbol(refed)]: false,
  [Symbol(kHasPrimitive)]: false,
  [Symbol(asyncId)]: 2,
  [Symbol(triggerId)]: 1
}

The myGreeting() function is not printed on the console. Notice the [Symbol(refed)] is set to false.

ref()

The ref() method is called if the Timeout object’s event loop is no longer active. The ref() method is only used after timeout.unref() has been called and you need to access the Timeout object once more:

const myTimeout = setTimeout(myGreeting, 5000, "Pascal");
function myGreeting(arg) {
  console.log("Welcome back " + arg)
}

// Checking the myTimeout.hasRef() object
console.log(myTimeout.hasRef());

// Unreferencing
console.log(myTimeout.unref());

// Referencing again
console.log(myTimeout.ref());

For the above example, the output will be as follows:

true
Timeout {
  _idleTimeout: 5000,
  _idlePrev: [TimersList],
  _idleNext: [TimersList],
  _idleStart: 73,
  _onTimeout: [Function: myGreeting],
  _timerArgs: [Array],
  _repeat: null,
  _destroyed: false,
  [Symbol(refed)]: false,
  [Symbol(kHasPrimitive)]: false,
  [Symbol(asyncId)]: 2,
  [Symbol(triggerId)]: 1
}
Timeout {
  _idleTimeout: 5000,
  _idlePrev: [TimersList],
  _idleNext: [TimersList],
  _idleStart: 73,
  _onTimeout: [Function: myGreeting],
  _timerArgs: [Array],
  _repeat: null,
  _destroyed: false,
  [Symbol(refed)]: true,
  [Symbol(kHasPrimitive)]: false,
  [Symbol(asyncId)]: 2,
  [Symbol(triggerId)]: 1
}
Welcome back Pascal

The myGreeting() function is printed on the console, and you’ll notice the [Symbol(refed)] is set to true.

How to throttle API requests using timers

Throttling API requests with Node.js timers can regulate the rate at which requests are sent to an API, thereby reducing excessive consumption or server overload. Let’s demonstrate a common technique for utilizing timers to achieve request throttling:

  1. Determine how frequently you wish to make API calls
  2. Create a variable to keep track of the last API request’s timestamp. You should initially set it to the current timestamp
  3. While making a request, check the time passed since the previous request. If it is less than the desired time interval, the request should be delayed and scheduled for later execution
const desiredTimeInterval = 2000; //Milliseconds
let lastRequestTimestamp = Date.now();

function makeRequest() {
  const currentTimestamp = Date.now();
  const timeElapsed = currentTimestamp - lastRequestTimestamp;

  if (timeElapsed < desiredTimeInterval) {
    const delay = desiredTimeInterval - timeElapsed;
    setTimeout(makeRequest, delay);
  } else {
    // Make the API request here
    lastRequestTimestamp = Date.now();
  }
}

makeRequest();

When the API call is successful, the lastRequestTimestamp is updated to the current timestamp. When you wish to start an API request, invoke the makeRequest() function, which will manage the throttling, and, if required, hold off the requests.

The code above will space out API calls based on the specified interval, preventing an overflow of requests and maintaining the defined rate limitation.

Proper error handling with timers

When using timers in Node.js, error handling is critical to guarantee that any possible errors or exceptions that arise during timer-based processes are handled appropriately. Let’s consider some important factors for dealing with timer errors.

Error handling within timer functions

It’s important to include a try...catch  block within the callback functions provided by timer APIs like setTimeout() or setInterval(). This lets you capture any synchronous errors that could arise during the timer callback execution.

Handling errors in asynchronous operations

If the timer callback contains asynchronous operations like API requests or database access, errors must be handled inside the asynchronous functions themselves. You can use promises to properly manage asynchronous errors.

Clearing timers on error

If an error occurs and you want to end the timer, make sure you clear the timer using the proper function, either clearTimeout() or clearInterval(). This will stop the timer callback from being executed again.

Logging and reporting errors

When an error happens inside a timer callback or an asynchronous activity triggered by a timer, the error information must be logged for debugging. To log errors, you can utilize logging frameworks or Node.js’s built-in logging features like console.error(). You should consider implementing error monitoring and reporting technologies to acquire insight into production problems.

Conclusion

In Node.js, we can use timers to execute a function at a certain time or to delay a program or code execution. In this article, we discussed the different timer functions available in Node.js to set or clear the execution of code. We also discussed two additional timer functions, unref() and ref().

We then considered the benefits of using setTimeout() recursively, comparing it to JavaScript’s window.setTimeout() method. Lastly, we considered some best practices to approaching error handling with Node.js timer functions.

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket. LogRocket Network Request Monitoringhttps://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Pascal Akunne A JavaScript developer focused on building human-centric products with HTML, CSS, React, Node.js, and MongoDB

Leave a Reply