Search code examples
javascriptasynchronouspromisees6-promiseexecutioncontext

JavaScript - sync + asyns code exectuction order - I want to understand why the logs are displayed in that order


Here is my piece of Vanilla TS code that I am trying to understand:

console.log("-------------------------- (LOG 1)");

setTimeout(() => console.log("-----------------------------(LOG 2)"), 1000);

const fetchedData = fetch("https://pokeapi.co/api/v2/pokemon/ditto");

const fnReturningNewPromise = () => {
  return new Promise<string>((resolve, reject) => {
    if (1 == 1) {
      console.log("LOG 3 - new Promise in return Statement");
      resolve("fnReturningNewPromise - resolved");
    }

    reject("fnReturningNewPromise - rejected");
  });
};

const customPromise = new Promise<string>((resolve, reject) => {
  if (1 === 1) {
    console.log("LOG 4 - new Promise");
    resolve("Promise resolved");
  } else {
    reject("REJECTED");
  }
});

fetchedData
  .then((res) => {
    console.log("LOG 5 - fetchedData 1st then - res.json()");
    return res.json();
  })
  .then((data) => console.log("LOG 6 - with fetching"));

Promise.resolve(1).then((res) =>
  console.log("LOG 7 - immidielty resolved", res)
);

customPromise
  .then((response) => response)
  .then((data) => console.log("LOG 8 - without fetching"));

console.log("LOG 9 - sync code");

setTimeout(() => {
  console.log("LOG 10 - setTimeout - immidietly resolved");
}, 0);

fnReturningNewPromise().then((res) =>
  console.log("LOG 11 - resolving promise returned from fn: ", res)
);

And this is the ouput from the console:


-------------------------- (LOG 1) 
LOG 4 - new Promise 
LOG 9 - sync code 
LOG 3 - new Promise in return Statement 
LOG 7 - immidielty resolved 1
LOG 11 - resolving promise returned from fn:  fnReturningNewPromise - resolved 
LOG 8 - without fetching 
LOG 10 - setTimeout - immidietly resolved 
LOG 5 - fetchedData 1st then - res.json() 
LOG 6 - with fetching 
-----------------------------(LOG 2)

console screenshot

I try to find out where the order of this logs come from. Here are my questions:

  1. Why LOG 4 is displayed before LOG 9 - LOG 9 is pure sync code
  2. According to above - why LOG 3 is not displayed before LOG 4 - the only difference between them is that LOG 3 is in function's return statement
  3. Why LOG 7, LOG 11 and LOG 8 - I can imagine that LOG 7 is first as it is immidietly resolved, but why LOG 8 is displayed before LOG 11? (Especially that in the 1st part of code, LOG 4 (related to LOG 8) is displayed before LOG 3 (related to LOG 11)
  4. Why LOG 10 is not at the end? - this is setTimeout which goes into the task queue - I assume it should be outputted just before the finish line (LOG 2) as all the others are promise related logs (that have higher priority)

I got confused - can anyone explain it to me?

I tried to output the result in the console.log however the final effect confused me even more


Solution

  • Promise executor functions are executed synchronously.

    So going through the code looking for synchronously executed code the logs are

    • "-------------------------- (LOG 1)"

    • "log 4 - new Promise" from the executor of custom promise

    • "log 9 - sync code" from inline code

    • "LOG 3 - new Promise in return Statement", from a promise executor in a synchronously called function ("in return statement" is not an exact description).

    Jobs in the promise job queue are executed before tasks sourced from the the timer task queue

    So looking through for jobs placed in the promise job queue (i.e. the microtask queue) next we get

    • "LOG 7 - immidielty resolved"
    • "LOG 11 - resolving promise returned from fn: "

    Jobs in the promise job queue can add more jobs to run before the queue becomes empty

    So next we get jobs placed in the promise job queue by promise jobs in the queue that have already been executed:

    • "LOG 8 - without fetching")

    Timout callback jobs have lower priority than jobs in the Promise jobs queue

    So now timeout handlers from the timer task source can be executed because the promise job queue is empty:

    "LOG 10 - setTimeout - immidietly resolved")

    • "-----------------------------(LOG 2)"

    Logs from fetching

    When logs from the fetch operation appear depends on how long the operation takes to complete, but

    • "LOG 5 - fetchedData 1st then - res.json()" will appear before
    • "LOG 6 - with fetching"

    From actual output they appear within less than a second.

    Tech notes

    The task of running a promise handler to monitor its behavior and continue chained promise handling is called a "Promise job" in the above because that is what it is called in ECMAScript standards.

    Jobs in the Promise Job Queue get their priority over other tasks because they are implemented in HTML5 using the microtask queue.