Asynchronous code and events
Trace synchronous code, promises, and timers
Predict a Node.js program’s output, trace when callbacks read changing state, and compare calling a callback with scheduling it for later.
In this lesson we’ll predict when callbacks run and which values they read. That helps when a callback sees a value that changed after you scheduled it.
The event-loop guide explains the call stack with diagrams. Keep it nearby if you need a visual explanation. Here, we’ll work through a different program.
Predict the output
Save this as delivery-order.cjs. The .cjs extension makes this a CommonJS script, regardless of your project’s module settings.
Read it first. Write down the output before running it.
let delivery = 'packing'
function report(label) {
console.log(label, delivery)
}
console.log('start')
setTimeout(() => report('timer'), 0)
Promise.resolve().then(() => report('promise'))
report('now')
delivery = 'ready'
console.log('end')
Run it from the folder where you saved it:
node delivery-order.cjs
The output is:
start
now packing
end
promise ready
timer ready
report('now') is a regular function call. It runs before we change delivery to 'ready'.
The promise handler and timer callback run after this synchronous code finishes. Both read delivery when they run, so both see 'ready'.
The resolved promise’s handler runs as a microtask before the timer callback in this program. A zero-delay timer still waits for the main thread to become available.
Keep the value from scheduling time
Suppose the timer should report the status we had when we scheduled it. Save that string before scheduling the callback.
Replace the timer line with these lines, keeping the rest of the program:
const scheduledDelivery = delivery
setTimeout(() => console.log('timer', scheduledDelivery), 0)
Run the file again. The timer now prints timer packing, while the promise still prints promise ready.
We’ve kept a separate string value. Closing over an object would keep a reference to that object, so later changes to its properties would still be visible.
Does accepting a callback make a function asynchronous?
Replace the file with this smaller example. Predict whether before or after appears beside callback.
let status = 'before'
function notify(callback) {
callback()
}
notify(() => console.log('callback', status))
status = 'after'
console.log('finished', status)
It prints:
callback before
finished after
notify() calls the callback synchronously. Taking a function as an argument does not schedule it.
Now change callback() inside notify() to setTimeout(callback, 0). Run the file again. You should see finished after, followed by callback after.
Before moving on, explain why the callback reads a different value even though its body stayed the same. In the next lesson, we’ll look at Node’s separate next-tick queue.
Lesson completed