skip to Main Content

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));

2

Answers


  1. 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'))
    Login or Signup to reply.
  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; }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search