2

I have the following set of instructions

console.log(1)
setTimeout(() => {
  console.log(3)
});
Promise.resolve().then(() => console.log(4))
console.log(7)

The output is:

1
7
4
3

The order of execution is as follows:

  • consoles are executed
  • promise resolved
  • settimeout executed

Why is the promise resolved before the setTimeout? Both are handled by a callback, right?

Zameer Ansari
  • 28,977
  • 24
  • 140
  • 219
  • Promises have priority over timers. So, if both are in the event queue at the same time, then the promise will get serviced first. – jfriend00 Jul 05 '20 at 20:16

2 Answers2

5

Interesting question. In order to understand the reason, one needs to understand the JavaScript event loop.

.then() queues a microtask. Microtasks are executed as soon as the JS call stack empties.

In this case (but not in all cases), setTimeout queues a task in the main task queue. Tasks are queued for external events, timer handlers, rendering (in the browser), etc... If a task calls JS code, the next task will not run until all the microtasks associated with it finish running.

So here's what's happening:

  1. Task #1, queued internally by Node.js:
    1. console.log(1) logs 1. Output is 1
    2. setTimeout(() => { console.log(3) }); queues a task to log 3
    3. Promise.resolve().then(() => console.log(4)) queues a microtask to log 4
    4. console.log(7) logs 7. Output is 1 7.
    5. The JS stack empties, because there's no more statements. The microtasks begin to execute.
      1. Microtask #1-1, queued by .then():
        1. Logs 4. Output is 1 7 4.
  2. Task #2, queued by setTimeout():
    1. Logs 3. Output is 1 7 4 3.
    2. The JS stack empties, because there's no more statements, however there are no microtasks. Final output:
1
7
4
3
D. Pardal
  • 6,173
  • 1
  • 17
  • 37
  • Do all promises go in the microtask queue? – Gary Aug 02 '20 at 22:44
  • All `.then()` and `.catch()` callbacks do, as does code after `await` in an async function. – D. Pardal Aug 03 '20 at 08:48
  • 1
    _".then() queues a microtask"_ - technically, `then` method call queues a job in the micro-task queue to execute the fulfilment handler if the promise is _already resolved_. If the promise is in pending state, `then()` method call appends the fulfilment handler to an internal list containing the fulfilment handlers for a promise. – Yousaf May 21 '22 at 15:45
1

The Promise .then() callback will be invoked immediately after the code block completes. The timer will get a default minimum time of around 15 or 16 milliseconds, so it's definitely going to happen after that.

edit — apparently in this crazy modern world the minimum time is around 4ms, not 15 or 16.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • I appreciate this answer and I understand that by the sequence of execution but why it's definitely going to happen after that? – Zameer Ansari Jul 05 '20 at 19:37
  • 2
    Not quite... `setTimeout` is instantaneous unless it's called multiple times. In that case, it has a minimum of 4ms. See [this](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Timeouts_throttled_to_%E2%89%A5_4ms). – D. Pardal Jul 05 '20 at 19:39
  • @D.Pardal uhh no, there's still a few milliseconds of minimum delay. On top of that, there are separate event queues for Promise resolution and things like timers. You misread the very documentation you posted. – Pointy Jul 05 '20 at 19:42
  • 2
    That throttle is only relevant if there are multiple pending timeouts scheduled. There's no throttle for only one timeout: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Reasons_for_delays_longer_than_specified – Patrick Roberts Jul 05 '20 at 19:51
  • 1
    As a reality check, try this: `console.time('timeout'); setTimeout(() => console.timeEnd('timeout'), 0);` For me, it logs ~1.35ms – Patrick Roberts Jul 05 '20 at 19:56