Certified Associate JavaScript Programmer study materials
Certified Associate JavaScript Programmer study resources

Zero-Delay Timer vs Promise Callback in a Checkout Widget

Certified Associate JavaScript Programmer · Put your knowledge to work in context.

All case studies

Zero-Delay Timer vs Promise Callback in a Checkout Widget

A checkout page delegates post-click work to a small module. The module runs:

console.log('start');
setTimeout(() => console.log('timeout-0'), 0);
Promise.resolve().then(() => console.log('promise-1')).then(() => console.log('promise-2'));
setTimeout(() => console.log('timeout-50'), 50);
console.log('end');

The developer expected timeout-0 immediately after end, but the console shows both promise callbacks first. A second symptom appears when a 40 ms busy loop is inserted at the top of the timeout-0 callback: the timeout-50 message is delayed, while the promise messages are not. The team must document the real order and decide whether a required DOM update should move from the promise callback to a zero-delay timer. The requirement is that the update happen after the current click handler returns and before the browser paints.

1. List the exact console order for the original snippet and explain the rule that produces it.

Read the worked answer

The order is start, end, promise-1, promise-2, timeout-0, timeout-50.

Reasoning:

  1. The two console.log calls run synchronously on the call stack, so start and end come first.
  2. Promise.resolve().then(...) schedules a microtask. Chained .then callbacks queue another microtask after the first settles.
  3. setTimeout schedules macrotasks. A 0 delay does not mean "run now"; it means "run in a later timer turn, subject to clamping and queue order."
  4. After the synchronous stack empties, the event loop drains the entire microtask queue before taking the next macrotask. Therefore promise-1 and promise-2 run before either timer.
  5. Among timers, timeout-0 was registered first and has the shorter delay, so it precedes timeout-50.

The key distinction is not "promises are faster than timers" but "microtasks are drained to completion before the next macrotask."

2. Why does the 40 ms busy loop delay `timeout-50` but not `promise-1` and `promise-2`? What does that reveal about JavaScript concurrency?

Read the worked answer

By the time the busy loop runs inside timeout-0, the microtask queue has already been drained. promise-1 and promise-2 have already executed, so the loop cannot delay them.

timeout-50 has not run yet. JavaScript has one call stack and one thread for executing script. While the busy loop occupies that thread, no other callback can run, including the timer callback whose deadline has arrived. The timer fires only after the loop finishes and control returns to the event loop.

This shows that timers do not run in parallel with JavaScript execution. The runtime may track time externally, but callback execution is serialized on the main thread. A long synchronous block delays every pending macrotask and also prevents rendering. It is not a demonstration that setTimeout is inaccurate by exactly 40 ms; the delay is caused by the main thread being unavailable.

3. Should the required DOM update go in the promise callback or in `setTimeout(..., 0)`? Recommend one and justify it against the before-paint requirement.

Read the worked answer

Use the promise callback, or explicitly queueMicrotask, rather than setTimeout(..., 0).

A microtask runs after the current click handler returns but before the browser proceeds to rendering. That matches "after the handler, before the next paint." A zero-delay timer is a macrotask; the browser may render before running it, and timer clamping can add further delay. Moving the update to a timer therefore risks a visible stale frame.

The caveat is that microtasks must stay small. If the update does heavy synchronous work, it can still block painting because the render step waits for the microtask queue to drain. Keep the microtask limited to state changes and DOM writes, and move expensive computation elsewhere. Also note that setTimeout(..., 0) is not equivalent to a microtask even when the measured delay is small; the queues and render boundaries differ.