call
clearInterval
. Here’s an example that runs every 5 seconds until the minute
rolls over, or 10 times, whichever comes first:
const
start
=
new
Date
();
let
i
=
0
;
const
intervalId
=
setInterval
(
function
() {
let
now
=
new
Date
();
if
(
now
.
getMinutes
()
!==
start
.
getMinutes
()
||
++
i
>
10
)
return
clearInterval
(
intervalId
);
console.log(
`
${
i
}
:
${
now
}
`
);
},
5
*
1000
);
We see here that
setInterval
returns an ID that we can use to cancel (stop) it later.
There’s a corresponding
clearTimeout
that works the same way and allows you to
stop a timeout before it runs.
setTimeout
, setInterval, and clearInterval are all defined on
the global object (window in a browser, and global in Node).
Scope and Asynchronous Execution
A common source of confusion—and errors—with asynchronous execution is how
scope and closures affect asynchronous execution. Every time you invoke a function,
you create a closure: all of the variables that are created inside the function (including
the arguments) exist as long as something can access them.
We’ve seen this example before, but it bears repeating for the important lesson we can
learn from it. Consider the example of a function called
countdown
. The intended
purpose is to create a 5-second countdown:
function
countdown
() {
let
i
;
// note we declare let outside of the for loop
console
.
log
(
"Countdown:"
);
for
(
i
=
5
;
i
>=
0
;
i
--
) {
setTimeout
(
function
() {
console
.
log
(
i
===
0
?
"GO!"
:
i
);
}, (
5
-
i
)
*
1000
);
}
}
countdown
();
Go ahead and work through this example in your head first. You probably remember
from the last time you saw this that something’s wrong. It looks like what’s expected is
a countdown from 5. What you get instead is
–1
six times and no
"GO!"
. The first
time we saw this, we were using
var
; this time we’re using
let
, but it’s declared out‐
side of the
for
loop, so we have the same problem: the
for
loop executes completely,
202 | Chapter 14: Asynchronous Programming