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
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.