Register to get access to free programming courses with interactive exercises

Timers JS: Asynchronous programming

In this lesson, we will discuss timers, which play a prominent role in the world of asynchronous programming.

Timers allow you to make any function execute later. The most important function for working with timers is setTimeout(f, delay):

const f = () => console.log('hey!');
setTimeout(f, 1000);

In the code above, the f function will wait for at least a second to execute because of the second parameter. It passes the time, specified in milliseconds, after which the function specified by the first parameter will start.

For historical reasons, timers have a minimum delay of four milliseconds. In other words, there is no difference between setTimeout(f, 1), setTimeout(f, 3), and setTimeout(f, 4). They all wait at least four milliseconds.

Timers are of many different uses. If we talk about browsers, they can be used for auto-hiding elements: notifications, for example. Another example is recurring Ajax requests to retrieve new data. On servers, timers are used less frequently but are also common. We can use them to break a large synchronous operation into several chunks, giving another code a chance to execute.

In operating systems, this behavior is called cooperative multitasking. It allows you to create a sense of parallel code execution, even if it isn't what's really happening.

If we pass a function into a timer, it does not execute on the current call stack. It means that all those features and approaches we mentioned earlier apply to timers.

Errors in timers can't be traced with try/catch, so you have to use callbacks for that:

const f = () => console.log('hey!');
console.log('before timeout');
setTimeout(f, 1000);
console.log('after timeout');
// the script doesn't end, it waits for the timers to execute

Let's run it:

node index.js

before timeout
after timeout
hey!

Do timers guarantee an exact start after a certain time? Actually, they don't.

It all depends on what happens at the moment. The runtime checks on timers when there is no code left in the current call stack. If you're doing a heavy calculation for a long time, all the callbacks, and all the timers, will wait for the calculation to complete.

It means that timers set a minimum time after which they can be started.

Out of this feature, we can draw two conclusions:

  • We should minimize the time of calculations by breaking them down into steps
  • We shouldn't count on the accuracy of call times, it will always be higher

Timers can be not only created but also canceled. The setTimeout call returns a special value, the timer ID.

If you pass it to the clearTimeout function, the timer will be canceled:

const f = () => console.log('hey!');
console.log('before timeout');
// In browsers, the timer identifier is a numeric value
// In node.js, it's an object
const timerId = setTimeout(f, 1000);
console.log('after timeout');
clearTimeout(timerId);

The code's running:

node index.js

before timeout
after timeout

The asynchronous function returns the timer ID. But this doesn't contradict what we said earlier about returns in asynchronous functions.

An asynchronous function cannot return the result of an asynchronous operation, but it can return other things performed synchronously within it. In the case of timers, the timer ID is returned synchronously, but the timer itself is executed asynchronously.

A common mistake for beginners is not passing the function to the timer but calling it. It usually happens when they need to pass some predefined arguments to a function:

const f = (message) => console.log(message);
console.log('before timeout');
setTimeout(f('hey!'), 1000);
console.log('after timeout');

Running:

node index.js

before timeout
hey!
timers.js:390
    throw new ERR_INVALID_CALLBACK();
    ^

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function

It didn't get to the last log because the script crashed on the setTimeout. It happened because it was expecting a function to come in, but it didn't. The call in the example returned undefined.

There are three ways to pass data into a function.

Additional parameters in setTimeout

At first, we can use additional parametres. Here all arguments passed to setTimeout after the second argument (time) automatically become arguments to the function that the timer calls:

const f = (a, b) => console.log(a + b);
setTimeout(f, 1000, 5, 8);
// =>  13

Wrapper Function

The most common way is to create a wrapper function. This method is better than the previous one because of its transparency. You can immediately see what's happening:

const f = (a, b) => console.log(a + b);
setTimeout(() => f(5, 8), 1000);
// =>  13

Function bind

The last way is to use the bind function. The main purpose of this function is to change the context of a function. But it can also be used if you want to apply the function partially:

const f = (a, b) => console.log(a + b);
// the first parameter is null because the context doesn't change
setTimeout(f.bind(null, 5, 8), 1000);
// =>  13

Calling this function returns a new function with the arguments applied.

Let's pay attention to the operation performed when we call the function passed to setTimeout.

Note that the timer does not make the operation asynchronous; it only delays its execution time. If the operation itself is synchronous, it will block the main thread of the program's execution once it starts, and all other operations will wait for its completion.

Function setInterval

The setInterval function has exactly the same signature as setTimeout. The essence of the arguments is the same.

The difference is that setInterval does not automatically start the function once. It executes until we explicitly stop it via clearInterval. The time between starts is equal to the value of the second parameter:

const id = setInterval(() => console.log(new Date()), 5000);
setTimeout(() => clearInterval(id), 16000);

// node index.js
// 2019-06-05T19:05:28.149Z
// 2019-06-05T19:05:33.172Z
// 2019-06-05T19:05:38.177Z

The timer stops, if we use its id in the callback:

let counter = 0;
const id = setInterval(() => {
  counter += 1;
  if (counter === 4) {
    clearInterval(id);
    return;
  }
  console.log(new Date());
}, 5000);

Recommended materials

  1. setTimeout
  2. setInterval

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

Bookmate
Health Samurai
Dualboot
ABBYY
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.