leaving
i
with the value
–1
, and only then do the callbacks start executing. The prob‐
lem is, when they execute,
i
already has the value
–1
.
The important lesson here is understanding the way scope and asynchronous execu‐
tion relate to each other. When we invoke
countdown
, we’re creating a closure that
contains the variable
i
. All of the (anonymous) callbacks that we create in the
for
loop all have access to
i
—the same
i
.
The tidy thing about this example is that inside the
for
loop, we see
i
used in two
different ways. When we use it to calculate the timeout (
(5-i)*1000
), it works as
expected: the first timeout is
0
, the second timeout is
1000
, the third timeout is
2000
,
and so on. That’s because that calculation is synchronous. As a matter of fact, the call
to
setTimeout
is also synchronous (which requires the calculation to happen so that
setTimeout
knows when to invoke the callback). The asynchronous part is the func‐
tion that’s passed to
setTimeout
, and that’s where the problem occurs.
Recall that we can solve this problem with an immediately invoked function expres‐
sion (IIFE), or more simply by just moving the declaration of
i
into the
for
loop
declaration:
function
countdown
() {
console
.
log
(
"Countdown:"
);
for
(
let
i
=
5
;
i
>=
0
;
i
--
) {
// i is now block-scoped
setTimeout
(
function
() {
console
.
log
(
i
===
0
?
"GO!"
:
i
);
}, (
5
-
i
)
*
1000
);
}
}
countdown
();
The takeaway here is that you have to be mindful of the scope your callbacks are
declared in: they will have access to everything in that scope (closure). And because
of that, the value may be different when the callback actually executes. This principle
applies to all asynchronous techniques, not just callbacks.
Error-First Callbacks
At some point during the ascendancy of Node, a convention called
error-first call‐
backs established itself. Because callbacks make exception handling difficult (which
we’ll see soon), there needed to be a standard way to communicate a failure to the
callback. The convention that emerged was to use the first argument to a callback to
receive an error object. If that error is
null
or
undefined
, there was no error.
Whenever you’re dealing with an error-first callback, the first thing you should do is
check the error argument and take appropriate action. Consider reading the contents
of a file in Node, which adheres to the error-first callback convention:
Callbacks | 203