Deep Dive into Promise Microtasks: How .then Registers and Executes

This article thoroughly dissects JavaScript Promise execution, revealing how microtasks are registered and run across multiple code snippets, compares Promise/A+ with WebKit implementations, and provides detailed code analyses and output predictions to help developers master Promise behavior.

WecTeam
WecTeam
WecTeam
Deep Dive into Promise Microtasks: How .then Registers and Executes

Preface

The article uses code‑by‑code analysis to explore the complete registration and execution process of Promise microtasks, going beyond the usual surface‑level knowledge.

First code snippet

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
}).then(() => {
  console.log("外部第一个then");
  return new Promise((resolve, reject) => {
    console.log("内部promise");
    resolve();
  }).then(() => {
    console.log("内部第一个then");
  }).then(() => {
    console.log("内部第二个then");
  });
}).then(() => {
  console.log("外部第二个then");
});

The output order is: 外部promise → 外部第一个then → 内部promise → 内部第一个then → 内部第二个then → 外部第二个then.

Second code snippet

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
}).then(() => {
  console.log("外部第一个then");
  new Promise((resolve, reject) => {
    console.log("内部promise");
    resolve();
  }).then(() => {
    console.log("内部第一个then");
  }).then(() => {
    console.log("内部第二个then");
  });
}).then(() => {
  console.log("外部第二个then");
});

Because the outer return is omitted, the registration order changes, producing the output: 外部promise → 外部第一个then → 内部promise → 内部第一个then → 外部第二个then → 内部第二个then.

Third code snippet

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
}).then(() => {
  console.log("外部第一个then");
  let p = new Promise((resolve, reject) => {
    console.log("内部promise");
    resolve();
  });
  p.then(() => {
    console.log("内部第一个then");
  });
  p.then(() => {
    console.log("内部第二个then");
  });
}).then(() => {
  console.log("外部第二个then");
});

Here the inner Promise is stored in a variable and its .then calls are registered synchronously, resulting in: 外部promise → 外部第一个then → 内部promise → 外部第二个then → 内部第一个then → 内部第二个then.

Fourth code snippet

let p = new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
});
p.then(() => {
  console.log("外部第一个then");
  new Promise((resolve, reject) => {
    console.log("内部promise");
    resolve();
  }).then(() => {
    console.log("内部第一个then");
  }).then(() => {
    console.log("内部第二个then");
  });
});
p.then(() => {
  console.log("外部第二个then");
});

The registration is non‑chained; both outer .then callbacks are queued after the inner Promise resolves, giving the order: 外部promise → 外部第一个then → 内部promise → 外部第二个then → 内部第一个then → 内部第二个then.

Core idea

Promise .then registration and execution are separate: registration follows the JavaScript call order (FIFO queue), while execution proceeds with synchronous code first, then microtasks, and finally macro‑tasks.

Promise/A+ vs WebKit implementation

The article compares a pure Promise/A+ library with the WebKit (Chrome/Safari) engine. It shows that adding return Promise.resolve() changes the microtask ordering in WebKit, causing extra .then callbacks to run earlier.

// Simplified Promise.resolve implementation from Promise/A+
Promise.resolve = function (value) {
  if (value instanceof Promise) return value;
  // ...type checks omitted for brevity...
  if (typeof value === 'object' || typeof value === 'function') {
    try {
      var then = value.then;
      if (typeof then === 'function') {
        return new Promise(then.bind(value));
      }
    } catch (ex) {
      return new Promise(function (resolve, reject) { reject(ex); });
    }
  }
  return valuePromise(value);
};

WebKit’s internal C++ resolver ultimately enqueues the resolved value as a microtask, which interacts differently with already‑registered outer .then callbacks.

Promise execution diagram
Promise execution diagram

Further examples illustrate how additional outer .then calls are affected by the return of Promise.resolve() in WebKit.

WebKit vs Promise/A+ output
WebKit vs Promise/A+ output

Conclusion

Understanding the separation between registration and execution of Promise .then callbacks is essential for predicting asynchronous flow, especially when dealing with different engine implementations.

References

[1]

Promises/A+ : https://github.com/then/promise

JavaScriptAsyncPromise
WecTeam
Written by

WecTeam

WecTeam (维C团) is the front‑end technology team of JD.com’s Jingxi business unit, focusing on front‑end engineering, web performance optimization, mini‑program and app development, serverless, multi‑platform reuse, and visual building.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.