Timers let you run a function later, or over and over on a schedule. They power countdowns, auto-saving, slideshows, notifications that fade out, and simple animations. Every timer takes a function to run, which makes timers the perfect place to use the callbacks and arrow functions from the last two lessons.
| Function | What it does |
|---|---|
| setTimeout(fn, ms) | Run a function once, after a delay. Returns an ID |
| clearTimeout(id) | Cancel a timeout that has not fired yet |
| setInterval(fn, ms) | Run a function repeatedly, every delay. Returns an ID |
| clearInterval(id) | Stop a repeating interval |
setTimeout(function, milliseconds);
The first argument is the function to run; the second is the delay in milliseconds. 1000 ms is one second.
function showMessage() {
console.log("Three seconds later");
}
setTimeout(showMessage, 3000);
// Or pass an anonymous function, often an arrow:
setTimeout(() => {
console.log("Two seconds later");
}, 2000);
setTimeout(showMessage, 3000) is correct. setTimeout(showMessage(), 3000) runs showMessage right away and hands the timer its return value instead. Same rule as addEventListener from Function Invocation.
This surprises almost everyone. Calling setTimeout does not stop JavaScript and wait. It schedules the function for later and immediately carries on with the next line:
console.log("Start");
setTimeout(() => console.log("Timer"), 3000);
console.log("End");
Even a delay of 0 does not mean "right now". It means "as soon as the current code has finished":
console.log("first");
setTimeout(() => console.log("third"), 0);
console.log("second");
The delay is the earliest the function can run, not a promise. Remember from JS Control Flow that JavaScript is single-threaded: it does one thing at a time. A timer cannot interrupt code that is already running, so if JavaScript is busy, the timer has to wait:
setTimeout(() => console.log("Timer finished"), 1000);
let i = 4e9;
while (--i > 0); // a busy loop that takes several seconds
The timer was set for one second, but "Timer finished" cannot appear until the loop is done, several seconds later, and the page is frozen the whole time. Browsers also slow timers down in background tabs to save battery.
Extra arguments after the delay are passed to the callback. Or wrap the call in an arrow function, which many people find clearer:
function remind(task, who) {
console.log(`${who}: time to ${task}`);
}
setTimeout(remind, 1000, "stretch", "Alex");
setTimeout(() => remind("drink water", "Alex"), 2000);
setTimeout returns an ID number. Keep it, and you can pass it to clearTimeout to cancel the timer before it fires:
const id = setTimeout(() => console.log("Self-destruct!"), 5000);
clearTimeout(id);
console.log("Crisis averted");
On a web page, the start and cancel are usually separate buttons, so the ID lives in a variable both functions can reach:
<button onclick="startTimer()">Start Timer</button>
<button onclick="stopTimer()">Stop Timer</button>
<p id="demo"></p>
<script>
let timer;
function startTimer() {
timer = setTimeout(() => {
document.getElementById("demo").innerHTML = "Finished";
}, 5000);
}
function stopTimer() {
clearTimeout(timer);
document.getElementById("demo").innerHTML = "Timer stopped";
}
</script>
setInterval calls the function every time the delay passes, until you stop it:
let seconds = 0;
const timer = setInterval(() => {
seconds++;
console.log(`${seconds}s`);
}, 1000);
An interval never stops on its own; it keeps going until you clear it or the page closes. Always keep its ID so you can end it:
let count = 3;
const countdown = setInterval(() => {
console.log(count);
count--;
if (count < 0) {
clearInterval(countdown);
console.log("Liftoff!");
}
}, 1000);
let clock;
function startClock() {
if (!clock) { // only if not already running
clock = setInterval(showTime, 1000);
}
}
function stopClock() {
clearInterval(clock);
clock = undefined; // so start works again
}
There is a second way to repeat: a function that runs, then schedules itself again with setTimeout:
function repeat() {
console.log("Hello");
setTimeout(repeat, 1000); // schedule the next run
}
repeat();
The difference is subtle but useful. With setInterval, the next run is scheduled at fixed intervals no matter how long each run takes. With repeated setTimeout, the next delay only begins after the current run has finished. Use it when one task must be completely done before waiting for the next, like checking a server for new messages.
This updates an element on the page every second. It needs a <p id="clock"></p> in the HTML:
function updateClock() {
const now = new Date();
document.getElementById("clock").innerHTML = now.toLocaleTimeString();
}
updateClock(); // show it immediately
setInterval(updateClock, 1000); // then refresh every second
new Date() is the current date and time; JS Dates covers it.)
For historical reasons, timers also accept a string of code: setTimeout("myFunction()", 1000). It works, but do not use it. The string is turned into code at run time, which is slower, hides typos until the moment it runs, and can be a security risk if any part of the string came from a user. Always pass a function: setTimeout(myFunction, 1000).
setTimeout(myFunction(), 1000). Remove the ()setTimeout / setInterval returns if you might need to cancel itif (!timer)clearInterval it or the page closesWhat order are these logged in?
console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");
A, C, B. Even with a 0 ms delay, the timer's function waits until the current code has finished.Which line runs save every 30 seconds?
setTimeout(save, 30)setInterval(save(), 30000)setInterval(save, 30000)setInterval(30000, save)save immediately and passes its result. D has the arguments in the wrong order.A user clicks "Start" three times, and the clock starts ticking three times per second. What is the fix?
setInterval if no timer is running (if (!clock)), and set the variable back to undefined when stopping.clearTimeout before it fires