Timing
Two in-built functions are useful for constructing a flow of code based on time: setInterval
and setTimeout
. The functions essentially allow you to schedule when code will run.
Intervals
setInterval
will continuously call whatever function you give it at the millisecond interval you set. Examples:
const showMessage = () => { ... omitted ... }
const cuckoo = () => { .. omitted ... }
// Calls 'showMessage' every 10s - 1s = 1000ms
setInterval(showMessage, 10*1000);
// Calls 'cuckoo' every 60 minutes
setInterval(cuckoo, 60*60*1000);
// Runs the anonymous function every second
setInterval(() => {
console.log("Tick!");
}, 1000);
To cancel the interval, you need to keep track of the scheduled execution, and pass this to clearInterval
:
// Start
let cuckooInterval = setInterval(cuckoo, 60*60*1000);
// User clicked a 'Stop' button, stop the timer
function onStopClick() {
clearInterval(cuckooInterval);
}
For practical purposes, you can depend on the scheduled code to run at the interval you specify, but be aware that the timing is not 100% precise. If the Javascript engine is busy with other work, or the device itself is overloaded, execution may be delayed.
Another consideration of setInterval
is that it doesn't take into account how long it actually takes your code to run. If you have code scheduled to run every second, but it actually takes five seconds for the code to finish running, you'll end up with lots of overlapped function calls - maybe not what you expect. See the Conditional intervals pattern below for a solution.
Read more
- setInterval MDN
Delays
setTimeout
calls a function after a millisecond delay has elapsed. Unlike setInterval
, the function only executes once. Examples:
// Calls 'showMessage' after 10 seconds
setTimeout(showMessage, 10*1000);
Use clearTimeout
to cancel a scheduled function call, just like with intervals.
let t = setTimeout(showMessage, 2*60*1000);
...
function onSomethingElseHappens() {
clearTimeout(t);
}
Conditional intervals
A common pattern is for a delayed function call to reschedule itself conditionally. The behaviour thus is like setInterval
, but rather than cancelling, you choose not to reschedule it.
The primary benefit of the self-scheduled interval is that it doesn't matter how long it takes your function to run, it will still wait the full delay before executing again.
function moveBox() {
// do something...
// Run again if there hasn't been any user interaction
if (!userInteraction)
setTimeout(moveBox, 1000);
}
moveBox(); // start it the first time
Note that calling moveBox()
more times will create additional, overlapping execution loops - not normally what you want.
Synchronisation
If you want to synchronise with the actual time - for example to call a function exactly on-the-hour, or on-the-minute, you can calculate the time difference and use that:
// Runs the next time the computer's clock switches to the next minute
// Get seconds returns 0..59 of the current time
// ie, if it's 20, it means it's 20 seconds past the minute,
// and we have to wait 40s until the next minute
const now = new Date();
const nextMinuteInMs = (60-now.getSeconds()) * 1000;
setTimeout(() => {
console.log('On the minute! It is now: ' + new Date());
}, nextMinuteInMs);
If you then want to keep executing code on-the-minute, we can refactor the code a bit to continually reschedule when to call onTheMinute
const onTheMinute = () => {
console.log('On the minute! It is now: ' + new Date());
schedule(); // Schedule for the next minute
}
const schedule = () => {
const now = new Date();
const nextMinuteInMs = (60-now.getSeconds()) * 1000;
// Schedule 'onTheMinute' to be called when we're next on an even minute
setTimeout(() => {
onTheMinute();
}, nextMinuteInMs);
}
// Schedule the first call
schedule();
Animation
You can certainly do basic animation with setInterval
or setTimeout
, even though it's a bad idea.
<body>
<div style="position:fixed" id="box">
BOX
</div>
</body>
const loop = () => {
const el = document.getElementById('box');
if (el.offsetLeft >= 1000) return; // Stop at some point
el.style.left = (el.offsetLeft + 10) + "px"
setTimeout(loop, 1000);
}
loop();
Processing a list
Here's an example of how to process an item from an array every x milliseconds:
const processLoopSlowly = (items, delay) => {
let position = 0; // Keep track of where we are
// Define a function within this function
const loop = () => {
// Do something with the item here...
console.log("Processing: " + items[position]);
position++;
if (position == items.length) {
console.log("Finished"); // We're done
} else {
setTimeout(loop, delay); // Otherwise, self-scheduled again
}
}
setTimeout(loop, delay);
}
const items = [
"Passionfruit", "Kiwifruit", "Mango", "Nectarines"
]
processLoopSlowly(items, 1000);