Without going into the basic concepts and ways to use Promise, let’s talk about how it works

Please read the Promise/A+ specification implementation code at the bottom

Firstly, I will clarify a concept: resolution refers to changing the state of the Promise from pending to fulfilled/ Rejected

Second, setTimeout in the Promise/A+ specification implementation code can be understood by adding code to the platform’s microtask queue for asynchronous execution

Describe the entire flow of the Promise chain call with code

  1. New Promise(excutor = (resolve, reject) => {}) returns a Promise object, obj

      try {
        excutor(resolve, reject);
      } catch (e) {
        reject(e);
      }
    Copy the code
  2. Perform obj. Then (onFulfilled onRejected)

    This is a big pity. First judge whether onFulfilled and onRejected are functions. If not, onFulfilled will be modified into value => value. OnRejected => {throw reason}

     onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value= > value;
     onRejected =
       typeof onRejected === "function"
         ? onRejected
         : reason= > {
             throw reason;
           };
    Copy the code
  3. Obj. Then returns a newPromise whose excutor method is related to the state of obJ at that time

    • If OBj is pending

      return newPromise = new Promise((resolve, reject) = > {
        that.onFulfilledCallbacks.push(value= > {
          try {
            let x = onFulfilled(value);
            resolvePromise(newPromise, x, resolve, reject);
          } catch(e) { reject(e); }}); that.onRejectedCallbacks.push(reason= > {
          try {
            let x = onRejected(reason);
            resolvePromise(newPromise, x, resolve, reject);
          } catch(e) { reject(e); }}); })Copy the code

      The then method does several things while OBJ is pending

      1. Create a newPromise object, newPromise, and return

      2. Execute newPromise’s Excutor to add callbacks to obJ’s two callback queues

      3. Add a success callback to obj. onledCallbacks: This is a big pity. Pass in obj.value and perform onFulfilled(value). Then perform the resolvePromise

      4. Add a failure callback to obj.onRejectedCallbacks: Pass obj.reason to onRejected(reason) and then resolvePromise. Obj. value is assigned to reject(reason)

    • If OBJ is already in the depressing state

      return newPromise = new Promise((resolve, reject) = > {
        setTimeout((a)= > {
          try {
            let x = onFulfilled(that.value);
            resolvePromise(newPromise, x, resolve, reject);
          } catch(e) { reject(e); }}); })Copy the code

      We also return a new Promise object whose excutor function will add the onFulfilled and resolvePromise to the microtask queue. That is, if OBJ executes then after the fulfilled Promise has become fulfilled, OnFulfilled will be executed immediately after the synchronization task is completed. The onFulfilled then method will not need to be added to the callback queue because it will not wait until OBJ executes resolve. The status of a Promise will not change once it is resolved.

    • If OBJ is in the Rejected state, the processing is basically the same as that in the fulfilled state

  4. If OBJ executes then in pending state, we wait for excutor to execute resolve or Reject

    • Resolve to perform

      Accept a value, and if value is also a Promise object, return value.then(reslove, reject)

      // Make sure that the value of a Promise resolution is never another Promise
      Promise1 = new Promise(resolve => resolve(promise2 = new Promise())
      // If the value of the resolution is promise2, the resolution of promisE1 cannot be continued
      // Execute promise2. Then (resolve, reject), wait for the resolution of promise2 before resolving the resolution of promise1
      
      if (value instanceof Promise) {
        return value.then(resolve, reject);
      }
      Copy the code

      Perform the next step asynchronously (where asynchrony is placed in the browser’s microtask queue) :

      This is a big pity. If obj.status === ‘pending’, change the status to ‘depressing’, assign value to obj.value, and execute all onledCallbacks. This will call the ondepressing function that we added in the THEN

    • Reject to perform

      Take a reason argument and perform the next steps asynchronously:

      If obj.status === ‘pending’, change the status to ‘depressing’, assign obj. Reason, and execute all onRejectedCallbacks. This calls the onRejected function that we added to THEN

    • About Asynchronous Execution

      We say promise. then is asynchronous, but executing the THEN method is not asynchronous. But onFulfilled and onRejected must be performed asynchronously, because resolve and Reject empty callbacks are performed asynchronously

  5. Perform onFulfilled(value) or onRejected(reason) to assign the returned value to variable X

    let x = onFulfilled(value);
    Copy the code
  6. Perform resolvePromise(promise2, X, resolve, Reject) to resolve promise2 according to X

    resolvePromise(newPromise, x, resolve, reject);
    Copy the code

    Note that promise2 is the Promise returned by then, resolve and Reject are resolve and Reject in excutor of promise2, In other words, the implementation of a resolvePromise resolves promise2

    Let’s take a closer look at what resolvePromise does

    This parameter x is the return value of onFulfilled(value). If onFulfilled is not a function, it will be written as v => v, which is also x = value

    And then what is x

    • If x === promise2, a circular reference error is reported

    • If X is a Promise object and its state is pending, then execute X. Teng to add ondepressing to the X.o ledCallbacks callback and wait for the X decision. Ondepressing (x.value) is fulfilled after X decision, which is also X. value => resolvePromise(promise2, X. value, resolve, reject). As mentioned above, x.value can never be a Promise

      x.then(
        y= >{ resolvePromise(promise2, y, resolve, reject); }, reason => { reject(reason); });Copy the code
    • If X is a Thenable object that handles the same as a Pending Promise object, here a called lock is used to avoid thenable. Then multiple calls to onFulfilled/onRejected

    • If x is a Promise object and its state is not pending, either x.value or X. value must exist. Execute X. value (resolve, reject) to resolve promise2. Resolve (x.value) or reject(x.rex)

    • Resolve (x) if x is a normal function/object, or some other data type

Rearrange the flow of chain calls

  1. promise1 = new Promise(excutor = (resolve, reject) => { … }) is executed immediately, but the final execution of resolve may be in an asynchronous operation

  2. Promise1. Then adds a callback to promise1 and returns a new resolution of promise2 that relies on the resolvePromise method of the previous callback

  3. Ondepressing (promise1. Value), which is passed in then, is assigned to variable X. Perform resolvePromise(promise2, X, promise2Resolve, promise2Reject)

  4. If x is a resolved Promise or a generic data type, then promise2Resolve(x) resolves promise2

  5. If x is a pending promise or thenable object, execute x. Chen, place resolvePromise in x’s success callback queue, and wait for x.value to be successfully assigned. Then execute resolvePromise(promise2, X. value, promise2Resolve, promise2Reject)

  6. In the meantime if you execute promise2. Then create a new one and return The new ondepressing (promise2. Value) and resolvePromise for promise3 are passed into the success callback queue of promise2 and wait for the resolution of Promise2

  7. Promise3. Then this implements the chain call

The sequence of chain calls

The resolution method of promisE1 is executed in the successful callback of promise1

Pass-through of chain calls

If the ondepressing passed in promise1. Then is not a function, then the ondepressing will be written as value => value. Value is passed to the resolvePromise of the promise2 to help it make a resolution. If value happens not to be a pending Promise or Thenable object, then promise2 will decide, Promise1. Value is then assigned to promise2. Value is then passed to promisE3, which is called passthrough

thinking

If a new PromiseA is fulfilled, the promise2 will directly take the state and value of this PromiseA for its own use, which happens in the resolvePromise.

If the value passed in to resolve is a new PromiseB, then the state and value of the resolve will be used as its own.

That is, we can pass in a newPromise during onFulfilled or a newPromise during resolve, Execute newPromise.then(resolve, reject) to suspend a Promise resolution (postpended) until the newPromise resolution.

Promise/A+ specification implementation


const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function Promise(excutor) {
  let that = this; 
  that.status = PENDING;
  that.value = undefined;
  that.reason = undefined;
  that.onFulfilledCallbacks = [];
  that.onRejectedCallbacks = [];

  function resolve(value) {
    
    if (value instanceof Promise) {
      return value.then(resolve, reject);
    }

    setTimeout((a)= > {
      if (that.status === PENDING) {
        that.status = FULFILLED;
        that.value = value;
        that.onFulfilledCallbacks.forEach(cb= >cb(that.value)); }}); }function reject(reason) {
    setTimeout((a)= > {
      if (that.status === PENDING) {
        that.status = REJECTED;
        that.reason = reason;
        that.onRejectedCallbacks.forEach(cb= >cb(that.reason)); }}); }try {
    excutor(resolve, reject);
  } catch(e) { reject(e); }}function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise!"));
  }

  let called = false;
  if (x instanceof Promise) {
    if (x.status === PENDING) {
      x.then(
        y= >{ resolvePromise(promise2, y, resolve, reject); }, reason => { reject(reason); }); }else{ x.then(resolve, reject); }}else if(x ! =null && (typeof x === "object" || typeof x === "function")) {
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          reason => {
            if (called) return;
            called = true; reject(reason); }); }else{ resolve(x); }}catch (e) {
      if (called) return;
      called = true; reject(e); }}else{ resolve(x); }}Promise.prototype.then = function(onFulfilled, onRejected) {
  const that = this;
  let newPromise;
  onFulfilled =
    typeof onFulfilled === "function" ? onFulfilled : value= > value;
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : reason= > {
          throw reason;
        };

  if (that.status === FULFILLED) {
    return (newPromise = new Promise((resolve, reject) = > {
      setTimeout((a)= > {
        try {
          let x = onFulfilled(that.value);
          resolvePromise(newPromise, x, resolve, reject);
        } catch(e) { reject(e); }}); })); }if (that.status === REJECTED) {
    return (newPromise = new Promise((resolve, reject) = > {
      setTimeout((a)= > {
        try {
          let x = onRejected(that.reason);
          resolvePromise(newPromise, x, resolve, reject);
        } catch(e) { reject(e); }}); })); }if (that.status === PENDING) {
    return (newPromise = new Promise((resolve, reject) = > {
      that.onFulfilledCallbacks.push(value= > {
        try {
          let x = onFulfilled(value);
          resolvePromise(newPromise, x, resolve, reject);
        } catch(e) { reject(e); }}); that.onRejectedCallbacks.push(reason= > {
        try {
          let x = onRejected(reason);
          resolvePromise(newPromise, x, resolve, reject);
        } catch(e) { reject(e); }}); })); }};Copy the code