-2

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

  • 2
    "*Why LOG 4 is displayed before LOG 9 - LOG 9 is pure sync code*" as is LOG 4: [When is the body of a Promise constructor callback executed?](https://stackoverflow.com/q/42118900) – VLAZ Apr 10 '23 at 05:42
  • "*why LOG 3 is not displayed before LOG 4 - the only difference between them is that LOG 3 is in function's return statement*" but you call the function that will log LOG 3 after creating the promise that will log LOG 4. – VLAZ Apr 10 '23 at 05:45
  • 2
    "*Why LOG 7, LOG 11 and LOG 8*" in general, you shouldn't rely on the resolution order of different promise chains. They might or might not behave as "expected" if your expectation is anything concrete. There is an explanation *here* but it's not globally applicable to anything that's not a toy example. – VLAZ Apr 10 '23 at 05:47
  • @VLAZ - it starts making sense to me. LOG 9 is being executed immediately as it is sync code, so it is code inside a new Promise assigned to customPromise. Then the last "sync" code is fnReturningNewPromise() [is it the last line of the file] - at that moment new Promise is born and the code inside of it is executed (a similar way that happened while assigning a new Promise to the variable a couple of lines above) as its body is sync. This is where "sync" code is finished. Then all of "thens" are registered. So at this stage, I wonder why LOG 11 is displayed after LOG 7? – TheCodemate Apr 10 '23 at 06:21
  • 1
    Considering your 3rd comment I belive it is displayed "randomly" and this order is not guaranteed in the future (it might or might not be displayed that way). is that true? If I want to make sure that these promises are resolved (or not) in a particular order I should have chained them together to resolve them one by one - step by step (in "sync" a like type of manner) – TheCodemate Apr 10 '23 at 06:28
  • It's not "random". The result is predictable *in this code*. However, *any* change in *anything* around this, even the other promise chains that are outside the 7/11/8 LOG statements might change the order. Again, it's not useful to expect anything specific from different promise chains. If you want to have a specific resolution order, then join the promise chains. Same if you want to use the results together. Otherwise if there is nothing in common, you just cannot expect an order. With code that *actually* does something async promise chain A might resolve before B or vice versa. – VLAZ Apr 10 '23 at 06:35
  • @VLAZ - thanks, you've helped me A LOT! I have a much clearer view of the subject. I think you've solved my problem – TheCodemate Apr 10 '23 at 06:39
  • @VLAZ - to finish it I would like to find out one more thing. Why the setTimeout is not executed at the end (after the fetching process). I consider it as a less prior than (it is registered in the task queue) while fetching is a promise based and is registered as a microtask (higher priority) – TheCodemate Apr 10 '23 at 06:46
  • The fetch takes time. While it's waiting for the network request, the LOG 10 timeout can execute. That's it. The microtask queue only takes priority when there is a task waiting on it. While the network request is being handled, there is nothing waiting. The follow-up of a `fetch().then(/* follow-up */)` only gets enqueued when the fetch is done. – VLAZ Apr 10 '23 at 06:59

2 Answers2

0

Callbacks passed to APIs like setTimeout are getting queued in the Task-queue. Functions in the task queue will only run, once the call stack is empty and there is no more synchronous code to be executed.

Promises behave differently. By using .then() or async/await you are setting functions to be executed once the promise resolves. These functions will go I to a different queue (micro task queue) and be executed as soon as the current stack finishes its synchronous code.

Egge
  • 256
  • 2
  • 7
  • Thanks @Egge - This is what I am aware of. Two different queues (Task and Microtask) but it does not clearly explains why setTimeout is executed before fetchedData? – TheCodemate Apr 10 '23 at 05:49
0

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.

traktor
  • 17,588
  • 4
  • 32
  • 53