Skip to main content

How does a "promise microtask" work?

What a microtask is

A microtask is a small task that runs after the current call stack, but before the engine moves on to macrotasks (for example setTimeout, setInterval, fetch, etc.).

Simply put: Microtasks are a "queue of urgent tasks" that run right after the current code, before the "next tick" of the Event Loop begins.

Promises create microtasks

When a promise moves to the fulfilled or rejected state, its .then() / .catch() / .finally() handlers do not run immediately, but are placed into the microtask queue.

Example

javascript
console.log('1'); Promise.resolve().then(() => console.log('2')); console.log('3');

Result:

javascript
1 3 2

Why:

  1. First, all the synchronous code runs (on the call stack).
  2. Then the Event Loop sees the stack is empty and runs all the microtasks (in this case, .then()).

Where microtasks are created

Microtasks are created:

  • by .then(), .catch(), .finally() on a Promise,
  • by queueMicrotask(fn),
  • by MutationObserver (in the browser),
  • by process.nextTick() in Node.js.

The microtask and macrotask queues

The JS engine processes events like this:

  1. Runs all the synchronous code (Call Stack).
  2. Runs all the microtasks, until the queue is empty.
  3. Moves on to one macrotask (for example, setTimeout).
  4. Runs all the microtasks that accumulated after it, again.
  5. And so on in a loop.

Example: comparison with macrotasks

javascript
console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); console.log('D');

Output:

javascript
A D C B

Why:

  1. A, D are synchronous;
  2. C is a microtask (from the promise);
  3. B is a macrotask (from setTimeout).

The microtask (C) runs before the timer (B).

Example with several microtasks

javascript
Promise.resolve().then(() => console.log('1')); Promise.resolve().then(() => console.log('2')); Promise.resolve().then(() => console.log('3')); console.log('4');

Output:

javascript
4 1 2 3

All the .then() calls land in the same microtask queue, and run in the order they were added, after the synchronous code finishes (4).

Example: a microtask inside a microtask

javascript
Promise.resolve().then(() => { console.log('A'); Promise.resolve().then(() => console.log('B')); });

Output:

javascript
A B

Why:

  • After A, a new microtask (B) runs, added while the previous one was running.

An important rule

After each macrotask finishes, the engine always fully runs all the microtasks, before picking up the next macrotask.

A simple analogy

Imagine the Event Loop is a queue at a cafe.

  • "Macrotasks" are regular orders (large ones).
  • "Microtasks" are handing back change quickly and settling up.

The barista takes an order (synchronous code), then hands out all the change (microtasks), and only then moves on to the next customer (a new macrotask).

Summary

Task typeExamplesWhen it runs
Microtasks.then(), .catch(), .finally(), queueMicrotask()After the current code, before the next macrotask
MacrotaskssetTimeout, setInterval, fetch, DOM eventsAfter all the microtasks of the current tick

Promises always use microtasks, which is why their callbacks run before timers.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.