JavaScript runs synchronous code on a call stack, one job at a time. In a browser, the host event loop schedules later work such as timer callbacks as tasks, while Promise reactions and queueMicrotask() callbacks run as microtasks. After a task finishes, the browser processes pending microtasks before moving on to later task work; that distinction explains the order of many asynchronous callbacks.
What the call stack does
The call stack tracks the execution contexts that are active right now. Calling a function adds its execution context to the top of the stack; returning removes it. The stack is not a callback queue: it describes nested, currently executing work.
On a JavaScript agent, a job runs to completion before another job is processed. As MDN puts it, “Each job is processed completely before any other job is processed.” Consequently, long-running synchronous work keeps that agent busy. A timer or Promise reaction cannot interrupt a synchronous function midway, and in a browser this can also delay UI responsiveness.
The JavaScript language’s execution model and a browser’s scheduling model are related, but they are not the same thing. The browser host decides when platform work can arrange for JavaScript callbacks to run. The MDN JavaScript execution model describes agents, execution contexts, and run-to-completion; the WHATWG HTML Standard defines the browser event-loop model.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstall#1 Best Overall
How browser tasks and microtasks differ
A browser event loop coordinates task work and microtasks. Tasks include work such as starting a script, dispatching certain events, or running a timer callback. The host model uses task sources and scheduling choices; it is misleading to imagine every kind of browser callback waiting in one universal, strictly ordered FIFO queue.
Promise reaction callbacks—such as functions registered with .then()—and callbacks passed to queueMicrotask() use the microtask queue. In the usual browser model, after a task completes, the browser performs a microtask checkpoint and drains the queue until it is empty. If a microtask adds another microtask, that new callback is processed in the same drain before later task work. A chain that keeps replenishing the queue can therefore delay other tasks and rendering opportunities. See MDN’s guide to microtasks.
Rank #2
Trace a timer and a Promise reaction
Consider this browser example:
console.log("start");
setTimeout(() => console.log("timer task"), 0);
Promise.resolve().then(() => console.log("promise microtask"));
console.log("end");
console.log("start")runs immediately.setTimeout()registers a timer callback. A delay of0makes the callback eligible as soon as the applicable timer constraints allow; it does not run synchronously or guarantee immediate execution.Promise.resolve().then(...)registers a Promise reaction, which is scheduled as a microtask.console.log("end")runs before the current synchronous job finishes.- At the microtask checkpoint, the Promise reaction runs. The timer callback is task work and runs later when the host selects its task.
For the usual browser behavior, the output order is start, end, promise microtask, then timer task. Promise reactions are deferred even when the Promise is already fulfilled. The Promise constructor’s executor is a different moment: it is called synchronously when the constructor runs, while a registered reaction such as a .then() callback is deferred. MDN explains these distinctions in Using promises.
What async and await change
Calling an async function starts executing its body and returns a Promise. When execution reaches await, the function suspends its continuation until the awaited value settles. Even when that value is an already-fulfilled Promise—or a plain value that is treated as a fulfilled Promise—the continuation is deferred rather than continuing synchronously at the await point.
This suspension does not block the main thread: unrelated code can run while the async function is waiting. But await does not make CPU-heavy synchronous work non-blocking. A loop that runs before yielding still occupies the JavaScript agent until it finishes. If the awaited Promise rejects, the rejection is thrown at the await point and can be handled with try/catch.
For details, see MDN’s reference for await.
How to reason about an unfamiliar example
- Mark the synchronous statements that run before the current job completes.
- Identify which callbacks are Promise reactions or
queueMicrotask()callbacks and which are host-scheduled tasks such as timer callbacks. - After the current task’s synchronous work finishes, account for the microtask checkpoint and include microtasks added by other microtasks.
- Only then consider later task work. For browser code, remember that host scheduling and task sources matter; do not infer a precise order between unlike tasks from a single global-queue picture.
Why runtime matters
The ordering above describes common browser behavior. The HTML event loop is a host specification, and event loops do not necessarily correspond one-to-one with implementation threads. Node.js and other JavaScript hosts have their own scheduling details, so browser task-order examples should not be transferred wholesale to them. For exact behavior in another runtime, consult that runtime’s documentation.
Quick Recap
Best Value
Rank #4
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




