0

Until this day I thought I knew how the event loop in javascript works, but I've faced a really strange issue. Maybe it's not strange for you, then I'd appreciate it if you can explain it to me, so here the example of code:

Promise.resolve()
  .then(() => {
      Promise.resolve().then(() => {
          Promise.resolve().then(() => {
              Promise.resolve().then(() => {
                  console.log('first mega inner then')
              })

              console.log('first very inner then')
          })

          console.log('first inner then')
      })

      console.log('first then')
  })
  .then(() => {
      Promise.resolve().then(() => {
          Promise.resolve().then(() => {
              console.log('second very inner then')
          })

          console.log('second inner then')
      })

      console.log('second then')
  })

Why is the order of console.logs is:

  1. first then
  2. first inner then
  3. second then
  4. first very inner then
  5. second inner then
  6. first mega inner then
  7. second very inner then

I personally expected it to be:

  1. first then
  2. first inner then
  3. first very inner then
  4. first mega inner then
  5. second then
  6. second inner then
  7. second very inner then

But it's not the end... The most interesting thing for me is when I add queueMicrotask after the second "then"

some code here...
.then(() => {
    Promise.resolve().then(() => {
        Promise.resolve().then(() => {
            console.log('second very inner then')
        })

        console.log('second inner then')
    })

    console.log('second then')
})

queueMicrotask(() => console.log('microtask'))

IT EXECUTES AFTER THE FIRST "THEN", so the order now is:

  1. first then
  2. microtask
  3. first inner then
  4. second then
  5. first very inner then
  6. second inner then
  7. first mega inner then
  8. second very inner then

What is going on? I understand nothing. I think this is the last thing I don't understand in JS and I will be very grateful to you if you can explain why it works like that, I won't be able to sleep till I know this

Rasul
  • 1
  • 1
  • 1
    This is all interesting on an intellectual level but you should NOT CARE the exact sequence the code inside the then is executed. If you MUST have a reliable order then you MUST do `promise.then().then().then().then()`. Do not nest it like this if you must know the order. So get your answer (for me I don't care or want to know the answer because it is harmful to writing good code) and then **IMMEDIATELY** forget about it. Your mentality must be that asynchronous code can complete at ANY time and order is NOT deterministic – slebetman Sep 20 '21 at 20:36
  • 1
    ..It is 100% perfectly OK for javascript to behave the way you see it or the way you expected it. Javascript may even behave differently in different browsers and it is perfectly 100% OK. Do not depend on knowledge of this kind of thing. – slebetman Sep 20 '21 at 20:37
  • 1
    .. WRITE code that resolves in the right sequence. Do not rely on accidental order due to implementation detail – slebetman Sep 20 '21 at 20:38
  • 1
    You never `return` the inner promises from the `.then()` callbacks, so you won't benefit from chaining. Basically you've got independent promise chains that just happen to run in lockstep with each other. – Bergi Sep 20 '21 at 20:40
  • @slebetman no, the order is specified (sure, shouldn't rely on that for a lot of reasons). – Jonas Wilms Sep 20 '21 at 20:44
  • I agree that I shouldn't care about those things, but anyway. There's rules everything works by in JS. And I'm trying to understand it. Even without those nested promises, why does that microtask happen after the first "then" and before the second? I think it's an important thing to know. – Rasul Sep 20 '21 at 20:44
  • Because the second one will only be called once the Promise it was attached to gets resolved. That happens once it's resolution is enqueued in the microtask queue, which will happen when the previous callback in the chain finished executing. – Jonas Wilms Sep 20 '21 at 20:49
  • 1
    You basically do `enqueueMicrotask(() => { /*1*/ enqueueMicrotask(() => /* 3 /* ); }) enqueueMicrotask(() => /*2*/)` – Jonas Wilms Sep 20 '21 at 20:50
  • learning about [chrome's debugging tools](https://developer.chrome.com/docs/devtools/javascript/) would be very useful for stepping through the order of operations code like this. – async await Sep 20 '21 at 22:01
  • @JonasWilms It may be specified (I don't care or want to know) but it would be 100% perfectly OK if the specification changes the behavior 5 years from now. One should never have any preconception of when an async function will be resolved or one would end up writing buggy code. If you NEED a specific order, WRITE the correct code to do it. Not depend on implementation detail like this. – slebetman Sep 21 '21 at 03:27
  • `why does that microtask happen after the first "then" and before the second? ` It's not important at all to know why. In real world code you never know when any asynchronous function will complete. When does a HTTP request complete? When does a video playback complete? As I said, if you NEED to care when something executes WRITE code to execute in the correct order. Not depend on implementation detail like this. – slebetman Sep 21 '21 at 03:30

1 Answers1

0

There may be a misunderstanding of how promise is used here. In promise chaining, the .then method is activated as soon as the promise resolves. If you want to delay the action of the second part of the chain, you need to specify when to resolve in a new promise. This example shows how that would work.

When you call Promise.resolve().then it calls the resolve method of the Promise class and makes the then method available immediately. Although you are not delaying the resolve when calling the resolve method immediately, it still takes some time to preform the resolve, so the following line of code will not be blocked.

Hopefull this provides some insight into the magic of Promise in js

(new Promise(resolve => {
  Promise.resolve().then(() => {
    Promise.resolve().then(() => {
      Promise.resolve().then(() => {
        console.log('first mega inner then')
        resolve();
      })
      console.log('first very inner then')
    })
    console.log('first inner then')
  })
  console.log('first then')
}))
.then(() => {
  Promise.resolve().then(() => {
    Promise.resolve().then(() => {
      console.log('second very inner then');;
    });
    console.log('second inner then');
  });
  console.log('second then');
});
async await
  • 1,967
  • 1
  • 8
  • 19
  • 1
    Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it) though! – Bergi Sep 20 '21 at 22:12
  • a cool read, thanks for the share – async await Sep 20 '21 at 22:13