Implementing a Promise on Your Own

Keywords: Javascript

Recently, I saw many articles about Promise source code parsing, or implementing a Promise by myself, and I suddenly wanted to make a wheel for myself to try.

First of all, the wheel does not fully comply with any standards and norms, but is written on the understanding and feelings of the regular Promise after use. It is purely entertainment.

The following is the implementation code:


// Object Type Judgment
const is = (typeAsString) => obj => Object.prototype.toString.call(obj) === typeAsString

// Determine whether it is an Error object
const isError = is('[object Error]')


/**
 * Customize Promise objects
 * @param {(resolve: (value: any) => void, reject: (reason: any) => void) => void} executor 
 */
function MyPromise(executor) {
  this.executor = executor
  // The initial state is pending...
  this.status = 'pending'

  /**
   * Internal decision state, only in pending state can continue to execute
   * @param {number} value 
   */
  function resolve(value) {
    if (this.status === 'pending') { // Ensure that only once is executed
      this.onfulfilled = ___onfulfilled.call(this, value)
    }
  }

  /**
   * To cache the execution results of the resolve method
   * @param {*} value value It's the result of the resolve method's execution.
   */
  function ___onfulfilled(value) {
    this.status = 'fulfilled'   // Change internal status
    /**
     * @param {(value: number) => void} onfulfilled Here are the parameters passed in the then method
     */
    return (onfulfilled) => {
      return onfulfilled(value) // 
    }
  }

  /**
   * The Method of Triggering Anomalies
   * @param {string} reason 
   */
  function reject(reason) {
    if (this.status === 'pending') {  // Ensure that only once is executed
      this.onrejected = ___onrejected.call(this, reason)
    }
  }

  /**
   * 
   * @param {Error} reason If the incoming object is not an Error object, it will be wrapped in Error
   */
  function ___onrejected(reason) {
    this.status = 'rejected'    // Change internal status
    return (onrejected) => {
      reason = isError(reason) ? reason : new Error(reason)
      return onrejected(reason)
    }
  }


  /**
   * @param {(value: number) => any} onfulfilled Processing Successful Functions
   * @param {(reason: any) => void} onrejected Handling functions with exceptions, if this parameter is passed in, the catch method will not catch it.
   */
  this.then = function(onfulfilled, onrejected) {
    const self = this
    return new MyPromise((resolve, reject) => {
      setTimeout(function waitStatus() {
        switch (self.status){
          case 'fulfilled': // resolved
            if (onfulfilled) {
              // Give the return value of the onfulfilled method to resolve to ensure that the next. then method can get the return value of the previous. then method
              const nextValue = self.onfulfilled(onfulfilled) 
              resolve(nextValue)
            } else {
              resolve()   // No incoming parameters, pretend to be incoming parameters:)
            }
            break
          case 'rejected':  // rejected
            if (!onrejected) { // If the onrejected parameter is not passed, a default implementation is implemented to ensure that the catch method can capture it
              onrejected = (reason) => reason
            }
            const nextReject = self.onrejected(onrejected)
            reject(nextReject)
            break
          case 'pending':   // 
          default:
              setTimeout(waitStatus, 0) // As long as it's pending, wait until it's not pending
              break
        }
      }, 0);
    })
  }

  /**
   * Catching exceptions
   * @param {(reason: any) => void} onrejected
   */
  this.catch = function(onrejected) {
    const self = this
    setTimeout(function reject() {
      if (self.status === 'rejected') {
        self.onrejected(onrejected)
      } else {
        setTimeout(reject, 0);
      }
    }, 0);
  }

  // Direct execution
  this.executor(resolve.bind(this), reject.bind(this));
}

At present, without considering the correctness of parameter input, assuming that all the parameters are correct, it can run normally in nodejs environment. The following is the test code:


// Override the default Proise object in the nodejs environment
global.Promise = MyPromise

async function test () {
  const r = await new Promise((resolve) => {
    setTimeout(() => {
      resolve(121)
    }, 1000);
  })
  console.log(r)    // Print 121
}

test()

The above code mainly uses the setTimeout method to check the internal state of Promise object, and make corresponding processing as soon as changes occur. The source code is here This is the case. Welcome to pat bricks.

Thanks for watching!

Posted by paparanch on Tue, 08 Oct 2019 20:31:51 -0700