0

I don't understand the order in which tasks are sent to the microtasks queue.
I expected this output: 1, 11, 111, 2, 3, 12, 122
But received: 1, 11, 111, 2, 12, 122, 3
Does compiler register the whole chain or it can see only the next then?
How does it work?

    Promise.resolve()
      .then(() => console.log(1))
      .then(() => console.log(2))
      .then(() => console.log(3));
    
    Promise.resolve()
      .then(() => console.log(11))
      .then(() => console.log(12));
    
    Promise.resolve()
      .then(() => console.log(111))
      .then(() => console.log(122));
JMP
  • 4,417
  • 17
  • 30
  • 41
  • 2
    The order is not guaranteed. 2 will always follow 1, as 3 will follow 2. 12 will follow 11, and 122 will follow 111. That those are you only guarantees. – Mr. Polywhirl May 18 '23 at 16:56
  • Event Loop is what you are looking for! Refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop – Shri Hari L May 18 '23 at 17:14
  • Related: 1. [Asynchronous Execution Order in JavaScript](https://stackoverflow.com/questions/68882535/asynchronous-execution-order-in-javascript) 2. [Promise chain .then .catch](https://stackoverflow.com/questions/68784426/promise-chain-then-catch) 3. [How to explain the output order of this code snippet?](https://stackoverflow.com/questions/63052649/how-to-explain-the-output-order-of-this-code-snippet) – Yousaf May 18 '23 at 17:36
  • Can you explain why you expected `3` to be logged immediately after `2`, before `12`? – Bergi May 18 '23 at 19:05

2 Answers2

-2

Linked Promise.then calls are nested, not sequential

According to MDN's Microtask guide,

if a microtask adds more microtasks to the queue by calling queueMicrotask(), those newly-added microtasks execute before the next task is run.

However, this does not apply to linked .then calls. If it did, you would have to operate under the assumption that linked .then calls are all linked to the original Promise and not each other. You can actually see that each linked .then call is linked to the result of the previous .then:

Promise.resolve(3)
  .then(x => { // Receives 3 from original Promise
    console.log(x)
    x = x ** 3
    return x
  })
  .then(x => { // Receives 27 from previous .then
    console.log(x)
    x = x ** 3
    return x
  })
  .then(x => { // Receives 19683 from previous .then
    console.log(x)
    return x
  })

In this example, you can see that in order for the next .then to be added to the queue, it needs the result of the previous .then call.

To further illustrate that linked Promise.then calls are nested and not sequential, you can create both nested and sequential microtasks to see what happens.

Microtasks

Nested

When this example is run, it clearly illustrates the same results you received from linking .then calls.

queueMicrotask(() => {
  console.log('A1')
  queueMicrotask(() => {
    console.log('A2')
    queueMicrotask(() => {
      console.log('A3')
    })
  })
})

queueMicrotask(() => {
  console.log('B1')
  queueMicrotask(() => {
    console.log('B2')
    queueMicrotask(() => {
      console.log('B3')
    })
  })
})

Sequential

When you run this example, it demonstrates the aforementioned quote from MDN. It functions similarly to how you thought linking .then calls would, but this is not how Promises work.

queueMicrotask(() => {
  console.log('A1')
  queueMicrotask(() => console.log('A2'))
  queueMicrotask(() => console.log('A3'))
  queueMicrotask(() => console.log('A4'))
})

queueMicrotask(() => {
  console.log('B1')
  queueMicrotask(() => console.log('B2'))
  queueMicrotask(() => console.log('B3'))
  queueMicrotask(() => console.log('B4'))
})

Promise conceptualization

If you don't care about this part, you can skip it. This is simply a quick implementation of a Promise using queueMicrotask. Hopefully it can help you see the nesting of linked .then calls and their associated microtasks.

class MicroPromise {
  #result = undefined
  #thens = []

  constructor(callback) {
    queueMicrotask(() => {
      callback(result => {
        this.#result = result
        this.#callThens()
      })
    })
  }
  
  then(callback) {
    return new MicroPromise(resolve => {
      this.#thens.push({resolve, callback})
    })
  }
  
  #callThens() {
    queueMicrotask(() => { // Allows us to wait for `.then` MicroPromise constructor microtasks to execute
      for (const then of this.#thens) {
        queueMicrotask(() => then.resolve(then.callback(this.#result)))
      }
    })
  }
  
  static resolve(result) {
    return new MicroPromise(resolve => resolve(result))
  }
}

MicroPromise.resolve()
  .then(() => console.log('A1'))
  .then(() => console.log('A2'))
  .then(() => console.log('A3'))

MicroPromise.resolve()
  .then(() => console.log('B1'))
  .then(() => console.log('B2'))
  .then(() => console.log('B3'))
Andria
  • 4,712
  • 2
  • 22
  • 38
-2

The order of the console logs are not guaranteed. There are 45 possible outcomes each time you run your script.

Here are your rules:

  • Main
    • 1 before 11
    • 11 before 111
  • Secondary
    • 2 follows 1
    • 3 follows 2
    • 12 follows 11
    • 122 follows 111

Demonstration

I put together a naïve script that will generate each permutation and validate the order based on the promise logic you have provided.

const sequence = [1, 2, 3, 11, 12, 111, 122];

const rules = [
 // All Promises
 { type: 'condition', operator: 'lt', left:   1, right:  11 },
 { type: 'condition', operator: 'lt', left:  11, right: 111 },
 // 1st Promise
 { type: 'condition', operator: 'gt', left:   2, right:   1 },
 { type: 'condition', operator: 'gt', left:   3, right:   2 },
 // 2nd Promise
 { type: 'condition', operator: 'gt', left:  12, right:  11 },
 // 3rd Promise
 { type: 'condition', operator: 'gt', left: 122, right: 111 }
];

const main = () => {
  const validPermutations = permutator(sequence)
    .filter(p => validate(p, rules));
  validPermutations.forEach((p, i) => {
    console.log(`Permutation #${i + 1}: ${JSON.stringify(p).replace(/,/g, ', ')}`)
  });
  console.log('Valid permutations:', validPermutations.length);
};

const validate = (sequence, rules) => {
  return rules.every(rule => {
    switch (rule.type) {
      case 'condition':
        const
          indexLeft = sequence.indexOf(rule.left),
          indexRight = sequence.indexOf(rule.right);
        switch (rule.operator) {
          case 'gt':
            return indexLeft > indexRight;
          case 'lt':
            return indexLeft < indexRight;
          default:
            return false;
        }
        break;
      default:
        return false;
    }
  });
};

const permutator = (inputArr) => {
  const result = [];
  permute(inputArr, result);
  return result;
}

const permute = (arr, res, m = []) => {
  if (arr.length === 0) res.push(m);
  else {
    for (let i = 0; i < arr.length; i++) {
      const curr = arr.slice(), next = curr.splice(i, 1);
      permute(curr.slice(), res, m.concat(next));
    }
  }
}

main();
.as-console-wrapper { top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132