4

I've been using Javascript for a while, and I understand how the javascript-event-loop works. However, I've come across a case that doesn't totally make sense for me. Consider the following code:

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

I expected this output:

3
1
2

However, I run it on chrome js console, and I get the following:

3
2
1

Question: shouldn't "Promise.resolve().then(fn)" either call the function immediately, or insert the function-execution-procedure to the end of the event-loop - the way 'setTimeout' works -, is there something I'm missing ?

  • One of the important behaviors of Promise callbacks is that they always run with a "clean stack". – Pointy May 08 '18 at 15:31
  • 1
    You can append a message to the current iteration of the event loop or prepend one to the next. `Promise` does the former and `setTimeout` the latter. Consequently you can block the event loop by recursively calling `then` but not by recursively calling `setTimeout`. –  May 08 '18 at 16:05

2 Answers2

3

Browsers implement multiple job/task queues. The spec requires implementations to maintain two queues:

  1. ScriptQueue
  2. PromiseQueue

There is most likely a TimerQueue (HTML DOM Task Queue) that accommodates the HTML DOM Level 2 Timer spec too. These are each FIFO queues that are populated at runtime and eventually are the event loop queue. Following your code example:

setTimeout(()=> console.log('1'));
Promise.resolve('whatever').then(()=>console.log('2'));
console.log('3');
  • Line 1 pushes onto (likely) a TimerQueue (HTML DOM Task Queue)
  • Line 2 pushes onto the PromiseQueue
  • Line 3 pushed onto the stack and is executed immediately (to completion)

Once the stack is empty, each queue will drain until empty. In your example, the Promise queue empties first and then the TimerQueue empties last.

This can be further demonstrated by extending your example just a bit:

setTimeout(()=> console.log('second from final')); // <-- first item in TimerQueue
Promise.resolve('whatever').then(()=>console.log('2')); //<-- first time in PromiseQueue
setTimeout(()=> console.log('just prior to final')); // <-- second item in TimerQueue
Promise.resolve('whatever').then(()=>console.log('1')); //<-- second item in PromiseQueue
setTimeout(()=> console.log('final')); // <-- third item in ScriptQueue 
console.log('3'); // <-- synchrounous function call placed onto the stack, executed immediately

You should note that order of execution is not guaranteed. The spec does not define the execution order of the queues:

This specification does not define the order in which multiple Job Queues are serviced. An ECMAScript implementation may interweave the FIFO evaluation of the PendingJob records of a Job Queue with the evaluation of the PendingJob records of one or more other Job Queues.

EDIT

After discussion (below) with Bergi I have indicated that browser implementations likely create other queues too. The most likely queue related to this post would be a TimerQueue as a Task Queue that holds the HTML DOM spec'd timer tasks.

Randy Casburn
  • 13,840
  • 1
  • 16
  • 31
  • 1
    "*Line 1 pushes onto the ScriptQueue*" - no. `setTimeout` isn't specced in ECMAScript at all, and it does not use the ScriptQueue. You have to look at the HTML5 timers spec for that. – Bergi May 08 '18 at 17:47
  • @Bergi - With due respect, every job that is a request for future execution is queued - regardless of the origin of that request. If it is not a Promise, the job is queued in the ScriptQueue. – Randy Casburn May 08 '18 at 17:51
  • 1
    Yes, they are all queued, but `setTimeout` doesn't even create a "job". It creates a "task". The term "job" is only used within the ECMAScript spec. – Bergi May 08 '18 at 18:00
  • A 1:1 relationship exists between HTML DOM Level 2 tasks/microtasks and ECMAScript Jobs. – Randy Casburn May 08 '18 at 18:32
  • That might well be, but I'm still sure that there are separate queues for scripts and for timers (and for promises, and for other stuff). – Bergi May 08 '18 at 18:51
  • Yes, I do believe there likely is a timer queue as well - if the implementation implements that. I can sight no references to that fact. Like you, I can only _suppose_ that those exist. So I took the straight line of referenced factual information which is the spec. The implementations may or may not include other queues, but according to the spec, they only must create those two. – Randy Casburn May 08 '18 at 19:04
  • [The spec relevant for browsers](https://www.w3.org/TR/html5/webappapis.html#integration-with-the-javascript-job-queue) goes into more details. It specifically says not to use the ScriptQueue, instead promise jobs go into microtasks while [timers have their own macrotask source](https://www.w3.org/TR/html5/webappapis.html#timer-task-source). – Bergi May 08 '18 at 19:19
  • I've modified the answer in anticipation of your previous comment – Randy Casburn May 08 '18 at 19:24
0

setTimeout behaves to a nuanced set of rules. For example, on several browsers, the minimum timeout is 4ms, not 0ms.

You are correct in that the Promise will not execute immediately, but will wait for a clean stack to run.

There are also tricks like adding a tag to forcibly bump something to the top of the event loop. See the setImmediate polyfill for browsers

AnilRedshift
  • 7,937
  • 7
  • 35
  • 59