0Pricing
JavaScript Academy · Lesson

Microtasks and Promises

Learn why promise callbacks run first.

Microtasks and Promises is a free JavaScript Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the JavaScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is a Microtask?

A microtask is a high-priority deferred job. Promise callbacks (.then, .catch, .finally) and queueMicrotask all schedule microtasks.

The key rule: after each synchronous run, all microtasks drain before any macrotask.

Promise Callbacks Are Deferred

Even an already-resolved Promise schedules its .then callback as a microtask. It does not run synchronously.

console.log('start');
Promise.resolve().then(() => console.log('promise then'));
console.log('end');

Microtasks Beat Macrotasks

This is the most important ordering rule. A Promise callback runs before a setTimeout callback, even if the timer was scheduled first.

setTimeout(() => console.log('timeout (macro)'), 0);
Promise.resolve().then(() => console.log('promise (micro)'));
console.log('sync');

The Microtask Queue Drains Fully

The event loop empties the entire microtask queue before taking the next macrotask. Multiple Promise callbacks all run together in that drain.

setTimeout(() => console.log('macro'), 0);
Promise.resolve().then(() => console.log('micro 1'));
Promise.resolve().then(() => console.log('micro 2'));
console.log('sync');

Chained Promises

Each .then in a chain schedules a new microtask when the previous one settles. They still all run before any pending timer.

setTimeout(() => console.log('timeout'), 0);
Promise.resolve()
  .then(() => console.log('then 1'))
  .then(() => console.log('then 2'))
  .then(() => console.log('then 3'));
console.log('sync');

Microtasks Scheduled During Drain

If a microtask schedules another microtask, the new one also runs in the same drain, before any macrotask. The queue keeps emptying until truly empty.

Promise.resolve().then(() => {
  console.log('micro A');
  Promise.resolve().then(() => console.log('micro B (added later)'));
});
setTimeout(() => console.log('macro'), 0);
console.log('sync');

Resolved Values Flow Through

A value returned from a .then handler becomes the input to the next handler in the chain, all via microtasks.

Promise.resolve(2)
  .then(n => n * 3)
  .then(n => n + 1)
  .then(n => console.log('result', n));
console.log('sync first');

async/await Uses Microtasks

Code after an await resumes as a microtask. So an awaited continuation runs before a timer scheduled earlier.

async function run() {
  console.log('a');
  await null;
  console.log('c (after await)');
}
setTimeout(() => console.log('d (timeout)'), 0);
run();
console.log('b');

A Combined Ordering Demo

Put it all together. Predict the order: synchronous first, then all microtasks, then the macrotask.

console.log('1 sync');
setTimeout(() => console.log('5 timeout'), 0);
Promise.resolve().then(() => console.log('3 promise'));
Promise.resolve().then(() => console.log('4 promise'));
console.log('2 sync');

Why Microtask Starvation Is Risky

Because microtasks always run before macrotasks, a microtask that endlessly schedules more microtasks can starve timers and rendering. Keep microtask chains finite.

let n = 0;
function loop() {
  if (n++ < 3) {
    console.log('micro ' + n);
    Promise.resolve().then(loop);
  }
}
setTimeout(() => console.log('macro after micros'), 0);
loop();

Why This Ordering Matters

Knowing microtasks run before macrotasks explains why Promise results appear before timer callbacks and why ordering bugs happen. The rule is simple: sync, then drain all microtasks, then one macrotask, repeat.

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

Quick Check

A Promise .then and a setTimeout(fn, 0) are both pending. Which runs first?

Recap: Microtasks and Promises

You learned the priority rule:

  • Microtasks come from Promises and queueMicrotask.
  • The whole microtask queue drains before any macrotask.
  • async/await continuations are microtasks.
  • Endless microtask chains can starve timers and rendering.

Frequently asked questions

Is the “Microtasks and Promises” lesson free?

Yes — the full text of “Microtasks and Promises” is free to read here on the web, and the JavaScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the JavaScript Academy course, upgrade to CoddyKit PRO.

What will I learn in “Microtasks and Promises”?

Learn why promise callbacks run first. You practise JavaScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start JavaScript Academy?

No prior experience is required. JavaScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Microtasks and Promises” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this JavaScript Academy lesson?

Yes. Every JavaScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The Call Stack
  2. Macrotasks and the Task Queue
  3. Microtasks and Promises
  4. queueMicrotask and Ordering
← Back to JavaScript Academy