Fulfill a Promise one step at a time

  1. When we use a promise, we use the new keyword to make a new promise (), so we should use a constructor or class. It’s 2021, so let’s implement it with class.
class MPromise {
    constructor(){}}Copy the code
  1. Define three state types
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
Copy the code
  1. Setting the initial state
class MPromise {
    constructor() {
        // The initial state is pending
        this.status = PENDING;
        this.value = null;
        this.reason = null; }}Copy the code
  1. Resolve and Reject methods

    1. According to the specification, these two methods change status from Pending to pity/Rejected.
    2. Note that the input arguments to both functions are value and reason.
class MPromise {
    constructor() {
        // The initial state is pending
        this.status = PENDING;
        this.value = null;
        this.reason = null;
    }

    resolve(value) {
        if (this.status === PENDING) {
            this.value = value;
            this.status = FULFILLED; }}reject(reason) {
        if (this.status === PENDING) {
            this.reason = reason;
            this.status = REJECTED; }}}Copy the code
  1. If you find that our promise is missing, let’s add it

    1. The input parameter is a function that takes resolve and reject.
    2. Note that when a promise is initialized, this function is executed and any errors are thrown through Reject
class MPromise {
    constructor(fn) {
        // The initial state is pending
        this.status = PENDING;
        this.value = null;
        this.reason = null;

        try {
            fn(this.resolve.bind(this), this.reject.bind(this));
        } catch (e) {
            this.reject(e); }}resolve(value) {
        if (this.status === PENDING) {
            this.value = value;
            this.status = FULFILLED; }}reject(reason) {
        if (this.status === PENDING) {
            this.reason = reason;
            this.status = REJECTED; }}}Copy the code
  1. Let’s implement the key THEN method

    1. Then receives two parameters, onFulfilled and onRejected
    then(onFulfilled, onRejected) {}
    Copy the code
    1. Check and process the arguments, ignoring the previous ones if they are not function. This ignore refers to the return of value or Reason as is.
    isFunction(param) {
        return typeof param === 'function';
    }
    
    then(onFulfilled, onRejected) {
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
            return value
        }
        const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
            throw reason;
        };
    }
    Copy the code
    1. The return value of then is a promise, so let’s wrap it around a promise and implement the rest of the logic later.
    then(onFulfilled, onRejected) {
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
            return value
        }
        const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
            throw reason;
        };
        const promise2 = new MPromise((resolve, reject) = > {})
        return promise2
    }
    
    Copy the code
    1. Depending on the current state of the promise, different functions are called
    then(onFulfilled, onRejected) {
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
            return value
        }
        const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
            throw reason;
        };
        const promise2 = new MPromise((resolve, reject) = > {
            switch (this.status) {
                case FULFILLED: {
                    realOnFulfilled()
                    break;
                }
                case REJECTED: {
                    realOnRejected()
                    break; }}})return promise2
    
    }
    Copy the code
    1. If you write it this way, it will be executed the moment the then function is called. This is a pity or Rejected. What if status is not fulfilled or Rejected? It may still be pending. Therefore, we need a monitoring mechanism for the state. When the state becomes a pity or Rejected, we will perform callback.

      1. So we need to get all the callback first, and then we can execute it at some point. Create two new arrays to store successful and failed callbacks, respectively, and then, if pending.
      FULFILLED_CALLBACK_LIST = [];
      REJECTED_CALLBACK_LIST = [];
      
      then(onFulfilled, onRejected) {
      const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
          return value
      }
      const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
          throw reason;
      };
      const promise2 = new MPromise((resolve, reject) = > {
          switch (this.status) {
              case FULFILLED: {
                  realOnFulfilled()
                  break;
              }
              case REJECTED: {
                  realOnRejected()
                  break;
              }
              case PENDING: {
                  this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled)
                  this.REJECTED_CALLBACK_LIST.push(realOnRejected)
              }
          }
      })
      return promise2
      
      }
      Copy the code
      1. When status changes, all callbacks are executed. Let’s use the getters and setters for ES6. This is more semantically appropriate for what to do when status changes.
      _status = PENDING;
      
      get status() {
          return this._status;
      }
      
      set status(newStatus) {
          this._status = newStatus;
          switch (newStatus) {
              case FULFILLED: {
                  this.FULFILLED_CALLBACK_LIST.forEach(callback= > {
                      callback(this.value);
                  });
                  break;
              }
              case REJECTED: {
                  this.REJECTED_CALLBACK_LIST.forEach(callback= > {
                      callback(this.reason);
                  });
                  break; }}}Copy the code
  2. The return value of then is a Promise. The return value of then is a Promise. The return value of then is a Promise.

    1. If ondepressing or onRejected throws an exception E, promise2 must reject the implementation and return the rejection cause E. (In this case, we need a manual catch code, reject when we encounter an error.)
    then(onFulfilled, onRejected) {
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
            return value
        }
        const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
            throw reason;
        };
        const promise2 = new MPromise((resolve, reject) = > {
            const fulfilledMicrotask = () = > {
                try {
                    realOnFulfilled(this.value);
                } catch (e) {
                    reject(e)
                }
            };
            const rejectedMicrotask = () = > {
                try {
                    realOnRejected(this.reason);
                } catch(e) { reject(e); }}switch (this.status) {
                case FULFILLED: {
                    fulfilledMicrotask()
                    break;
                }
                case REJECTED: {
                    rejectedMicrotask()
                    break;
                }
                case PENDING: {
                    this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
                    this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
                }
            }
        })
        return promise2
    }
    Copy the code

    7.2 If ondepressing is not a function and promise1 performs successfully, promise2 must perform successfully and return the same value

    7.3 If onRejected is not a function and promise1 rejects it, promise2 must reject it and return the same data.

    If onRejected succeeds, promise2 should be resolved

    We actually did this in the argument checking, which is this code

    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
        throw reason;
    };
    Copy the code

    7.4 If onFulfilled or onRejected returns a value x, then run the resolvePromise method

    then(onFulfilled, onRejected) {
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
            return value
        }
        const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
            throw reason;
        };
        const promise2 = new MPromise((resolve, reject) = > {
            const fulfilledMicrotask = () = > {
                try {
                    const x = realOnFulfilled(this.value);
                    this.resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e)
                }
            };
            const rejectedMicrotask = () = > {
                try {
                    const x = realOnRejected(this.reason);
                    this.resolvePromise(promise2, x, resolve, reject);
                } catch(e) { reject(e); }}switch (this.status) {
                case FULFILLED: {
                    fulfilledMicrotask()
                    break;
                }
                case REJECTED: {
                    rejectedMicrotask()
                    break;
                }
                case PENDING: {
                    this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
                    this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
                }
            }
        })
        return promise2
    }
    Copy the code
  3. resolvePromise

resolvePromise(promise2, x, resolve, reject) {
    // If newPromise and x point to the same object, reject newPromise as TypeError
    // This is to prevent endless loops
    if (promise2 === x) {
        return reject(new TypeError('The promise and the return value are the same'));
    }

    if (x instanceof MPromise) {
        // If x is a Promise, make newPromise accept the state of x
        // Continue to execute x, and if you get a y, continue to parse y
        queueMicrotask(() = > {
            x.then((y) = > {
                this.resolvePromise(promise2, y, resolve, reject); }, reject); })}else if (typeof x === 'object' || this.isFunction(x)) {
        // If x is an object or function
        if (x === null) {
            // null is also considered an object
            return resolve(x);
        }

        let then = null;

        try {
            // assign x. teng to then
            then = x.then;
        } catch (error) {
            // If an error e is thrown when taking the value x. teng, reject a promise based on e
            return reject(error);
        }

        // If then is a function
        if (this.isFunction(then)) {
            let called = false;
            // call x as the function's scope this
            // Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise
            try {
                then.call(
                    x,
                    // If resolvePromise is called with the value y, run resolvePromise
                    (y) = > {
                        // A variable called is required to ensure that it is called only once.
                        if (called) return;
                        called = true;
                        this.resolvePromise(promise2, y, resolve, reject);
                    },
                    // If rejectPromise is invoked with argument r, reject the promise with argument r
                    (r) = > {
                        if (called) return;
                        called = true;
                        reject(r);
                    });
            } catch (error) {
                // If calling the then method throws an exception e:
                if (called) return;

                // Otherwise use e as the basis for rejecting the promisereject(error); }}else {
            // If then is not a function, execute a promise with an x argumentresolve(x); }}else {
        // If x is not an object or function, execute a promise with x as an argumentresolve(x); }}Copy the code
  1. OnFulfilled and onRejected are micro tasks

    We can wrap the function in queueMicrotask

const fulfilledMicrotask = () = > {
    queueMicrotask(() = > {
        try {
            const x = realOnFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
            reject(e)
        }
    })
};
const rejectedMicrotask = () = > {
    queueMicrotask(() = > {
        try {
            const x = realOnRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
        } catch(e) { reject(e); }})}Copy the code
  1. Let’s just write some code and test it out
const test = new MPromise((resolve, reject) = > {
    setTimeout(() = > {
        resolve(111);
    }, 1000);
}).then(console.log);

console.log(test);

setTimeout(() = > {
    console.log(test);

}, 2000)
Copy the code
And now you can see why I can call.then, but I can't call.catch? Because we're not declaring a catch method in our classCopy the code
  1. Catch method
catch (onRejected) {
    return this.then(null, onRejected);
}
Copy the code
  1. promise.resolve

    The existing object will be fulfilled as a Promise object. If the parameter of the Promise. Resolve method is not an object with the then method (also called thenable object), a new Promise object will be returned, and its state will be fulfilled. Note that this is a static method because we call promise.resolve, not an instance.

static resolve(value) {
    if (value instanceof MPromise) {
        return value;
    }

    return new MPromise((resolve) = > {
        resolve(value);
    });
}
Copy the code
  1. promise.reject

    Returns a new Promise instance with the status Rejected. The reason argument to the promise. reject method is passed to the instance callback.

static reject(reason) {
    return new MPromise((resolve, reject) = > {
        reject(reason);
    });
}
Copy the code
  1. promise.race

    const p = Promise.race([p1, p2, p3]);

    This method wraps multiple Promise instances into a new Promise instance. If one instance of P1, P2, and P3 changes state first, then the state of P changes. The return value of the first changed Promise instance is passed to p’s callback.

static race(promiseList) {
    return new MPromise((resolve, reject) = > {
        const length = promiseList.length;

        if (length === 0) {
            return resolve();
        } else {
            for (let i = 0; i < length; i++) {
                MPromise.resolve(promiseList[i]).then(
                    (value) = > {
                        return resolve(value);
                    },
                    (reason) = > {
                        returnreject(reason); }); }}}); }Copy the code
Write some test codeCopy the code
const test = new MPromise((resolve, reject) = > {
    setTimeout(() = > {
        resolve(111);
    }, 1000);
});

const test2 = new MPromise((resolve, reject) = > {
    setTimeout(() = > {
        resolve(222);
    }, 2000);
});

const test3 = new MPromise((resolve, reject) = > {
    setTimeout(() = > {
        resolve(333);
    }, 3000);
});

MPromise.race([test, test2, test3]).then(console.log);
Copy the code