Timing in JavaScript
In this lesson we discuss
Date.now()
-
setTimeout
andsetInterval
- callbacks
What time is it?
Ironically, the normal way to tell the time in JavaScript is to use an object called Date.
Calling Date.now()
returns a very big number... try it now.
That number represents the number of milliseconds that have elapsed since January 1, 1970.
This value is also known as unix time or epoch time, and is a standard way of marking time in software... although as usual, JavaScript is not quite following the standards, since usually it's expressed in seconds, not milliseconds.
Lab: Unix Months
As a really quick exercise, calculate the number of months since 1970.
Hint: remember the Playing With Numbers lab?
Timers and Callbacks
The concept of callbacks is very common in JavaScript. In fact, you've probably used them already!
In this lesson we will focus on callbacks in the context of another very useful and common function, setTimeout
.
What is a callback?
A callback is just a function.
What makes it special is that you pass in as a parameter to a different function.
Later on, the callback function gets called back -- but not by your own code.
Rather, the function that you passed it in to in the first place either calls it immediately, or stores it away somewhere, so that it can be called when something else happens later on...
...possibly seconds or minutes or hours later!
setTimeout
The built-in function setTimeout
sets up a callback.
You call it with two parameters:
- a callback function (let's call it F)
- a number of milliseconds (let's call it N)
setTimeout
returns immediately, but also sets up a hidden timer
after approximately N milliseconds, F gets called back
Try this now:
function later() {
console.log("See you later...");
setTimeout( alligator, 1000 );
}
function alligator() {
console.log("Alligator!")
}
later();
setTimeout with inline callback
This "later alligator" program could be rewritten to use an anonymous inline function instead of a named top-level function.
function later() {
setTimeout( function() {
console.log("Alligator!")
}, 1000 );
console.log("See you later...");
}
later();
Using inline callbacks is a very common idiom in JavaScript, especially with setTimeout
.
The syntax can be confusing because of all the parentheses and curly braces, but it's essentially the same pattern as above:
- call
setTimeout
with two parameters - the first parameter is a function that prints "Alligator!"
- the second parameter is a number of milliseconds
-
setTimeout
will return immediately, then wait 1000 msec, then call the function
Note that even though "See you later" appears lower in the code than "Alligator", it happens first because
setTimeout
returns immediately.
Inline Function Parameter
Let's zoom in on the call to setTimeout
.
setTimeout( function() {
console.log("Alligator!")
}, 1000 );
- This code looks weird! It will probably take you a while before inline function arguments look and feel natural to you.
- The key to understanding this code is to remember that this is a normal function call with two parameters, just like
add(2, 2)
. - The weirdness comes from the fact that the first argument is not a function call. It's an entire function defined inline:
function() { console.log("Alligator!") }
The comma separating the parameters comes immediately after the inline function's closing brace:
"Alligator!") } , 1000
--------------/ | \---/
| | |
first arg | second arg
comma
Fat arrows can make inline function code more concise, and (to some) clearer. YMMV!
setTimeout( () => console.log("Alligator!") , 1000 );
Lab: measuring setTimeout's accuracy
In this lab, we will demonstrate that setTimeout
is not perfect.
(But hey, who is?)
Write a function named waitASecond
that...
- Gets the current time in milliseconds
- Saves it in a variable
- Uses
setTimeout
to set up a one second timer - When the callback executes, get the current time again
- Subtract the start time from the new current time and print that value
Now run the function several times in a row. What do you notice?
Click Here for Solution
function waitASecond() {
let start = Date.now();
setTimeout(
function() {
let end = Date.now();
console.log(end - start);
},
1000)
}
setInterval
setTimeout
has a sibling named setInterval
It works a lot like setTimeout
but is a little more complicated.
After calling setInterval
, JavaScript will call your callback again and again forever until you clear the timer.
For example:
function countDownFrom(num) {
let intervalId = setInterval(tick, 1000);
function tick() {
console.log(num);
num = num - 1;
if (num <= 0) {
console.log('Blastoff!');
clearInterval(intervalId);
}
}
}
countDownFrom(10);
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval
setInterval or setTimeout?
When you want something to happen again and again on a fixed delay, you need to choose between setInterval
and setTimeout
.
setInterval
is a bit more powerful but also a bit more complicated.
Note that anything you do with setInterval
could instead be implemented using setTimeout
, as long as your callback calls setTimeout
again recursively, like this:
function countDownFrom(num) {
setTimeout(tick, 1000);
function tick() {
console.log(num);
num = num - 1;
if (num <= 0) {
console.log('Blastoff!');
} else {
setTimeout(tick, 1000);
}
}
}
countDownFrom(10);
This design decision comes down to personal style and preference; the two solutions have about the same complexity and number of lines of code.
Testing Asynchronous Code
-
Use a "Mock Clock"
- the mock clock temporarily replaces
setTimeout
with a different function during tests - this function keeps track of what would be called when
- then "ticks" forward when asked
- so your tests can simulate speeding up and slowing down time
- the mock clock temporarily replaces
In Jasmine:
beforeEach(function() {
jasmine.Clock.useMock();
});
//... call the code that calls setTimeout
jasmine.Clock.tick(500); // advance 500 msec
- see thread How to test timers?
Links
- https://javascript.info/settimeout-setinterval - a nice tutorial on setTimeout and setInterval, including in-browser exercises
- https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
- https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval