Focus onThe front small OuRead more original technical articles

There are many Promise libraries on the market. This paper selects a lightweight Promise polyfill and implements the analysis step by step

If you’re not familiar with Promise, go ahead

Complete code + notes, can be read

Promise constructor – source code

/** The Promise constructor * argument fn: executor function (resolve,reject)=>{resolve(),reject()} * The executor function accepts two more arguments: resolve and reject callback functions */
function Promise(fn) {
  if(! (this instanceof Promise))
    throw new TypeError('Promises must be constructed via new') If the instance object is not an instance of Promise, an error is thrown
  if (typeoffn ! = ='function') throw new TypeError('not a function') // If the "executor function" passed is not of type function, an error is thrown

  /** internal status code * 0: pending, the current Promise is executing, the default value * 1: This will be fulfilled gradually, which is fulfilled gradually, and _value instanceof Promise === false * 2: Reject, which is fulfilled gradually, and _value instanceof Promise === false * 2: Reject; Someday, the resolve function is fulfilled, and _value is a Promise, that is, _value instanceof Promise === true */
  this._state = 0 // Default is 0

  /* Whether it has been processed */
  this._handled = false // Default is not processed

  /* Resolve value/reject reason (resolve or reject argument) */
  this._value = undefined // Undefined by default

  /** Hold an array of Handle instances, cache the callback passed by the then method * with three arguments: The ondepressing and onRejected callback method of the current promise, the promise to be implemented after the completion of the current promise * After the resolve() or reject() of the current promise is triggered, Loop through each Handle instance in the _Deferreds array and process the corresponding ondepressing and onRejected methods */
  this._deferreds = []

  /** Call the doResolve() method * argument fn: executor function * argument this: (scheduled) instance */
  doResolve(fn, this)}Copy the code
  • callnew PromisegeneratePromiseInstance, the argument (executor function) must befunctiontype
  • Internally sets the status code, whether it is processed, resolves the value/rejection reason, and caches the callback passed in by the then() method
  • Immediately calldoResolve()methods

DoResolve () – the source code

/** doResolve() method * argument fn: executor function (resolve,reject)=>{resolve(),reject()} * argument self: instance */
function doResolve(fn, self) {
  // console.log(self)

  var done = false // Initialize done, ensuring that resolve or reject is executed only once

  /* Execute the passed executor function fn */ immediately (and not asynchronously at this time)
  try {
    fn(
      // fn's resolve callback
      function (value) {
        if (done) return
        done = true
        /** * resolve() method * argument self: instance * argument value: resolve value */
        resolve(self, value)
      },
      Fn reject callback
      function (reason) {
        if (done) return
        done = true
        /** * resolve() method * argument self: (term) instance * argument value: rejection reason */
        reject(self, reason)
      }
    )
  } catch (err) {
    if (done) return
    done = true
    reject(self, err)
  }
}
Copy the code
  • Initialize thedoneforfalse, once implementedresolveorrejectThe callback assigns it totrueTo ensure that the callback is executed only once
  • Executing the executor function immediately confirms that the code is synchronized when the new Promise is executed
  • Immediately callresolve()(Resolve callback) orreject()The resolve callback or when an error is thrown
    • Is called if an error is thrownreject()methods

The resolve () – the source code

/** resolve() method * argument self: (term) instance * argument newValue: resolve value */
function resolve(self, newValue) {
  // console.log(self, newValue)
  try {
    if (newValue === self)
      throw new TypeError('A promise cannot be resolved with itself.') // The solution value cannot be approximately the instance itself (otherwise it will cause an infinite loop)
    if (
      newValue &&
      (typeof newValue === 'object' || typeof newValue === 'function') // If the solution value is an object or function object
    ) {
      var then = newValue.then
      if (newValue instanceof Promise) {
        // The solution value is of type PROMISE, _state = 3
        self._state = 3
        self._value = newValue
        finale(self)
        /* Self Promise {_state: 3, _handled: false, _value: Promise {... }, _deferreds: [] } */
        return
      } else if (typeof then === 'function') {
        // Resolve the thenable object (an object or function that has a THEN method) and continue doResolve on its THEN method
        // console.log(newValue)
        // console.log(newValue.then)
        // console.log(self)

        // doResolve(function () {
        // // The entire function is passed to doResolve() as the new executor method and called immediately, then() as the current resolve(), which executes then()
        // return thene.apply (newValue, arguments) // Return thene.apply (newValue, arguments)
        // }, self)
        doResolve(bind(then, newValue), self) // Override the above bind method with apply
        DoResolve (then, self) doResolve(then, self) doResolve(then, self

        return
      }
    }
    self._state = 1 // Resolve to other normal values, _state = 1
    self._value = newValue // Assign the solution value to the _value attribute of the contract instance
    // console.log(self)
    /** * Call () method * argument self: instance */
    finale(self)
  } catch (e) {
    reject(self, e)
  }
}
Copy the code
  • resolveThe callbackThe solution value cannot be approximately the instance itselfOtherwise it will cause an infinite loop
  • If it isPromiseObject, will be about_stateand_valueAssign values separately, then executefinale()methods
  • If it isthenableObject (notPromise)thenMethod continues executiondoResolve(usebindMethod to override this process, please refer to the comments.
    • The wholefunctionPassed as an actuator methoddoResolve()Immediately returns by calling the executor methodthen.apply(newValue, arguments)
    • Will solve the value ofthenMethod in vivothisPoint to the solution value itself and executethen()
  • If not the above two, will also contract_stateand_valueAssign values separately, also executedfinale()methods
  • Is called if an error is thrownreject()methods

The bind () – the source code

/** Polyfill for function.prototype.bind */
function bind(fn, thisArg) {
  return function () {
    fn.apply(thisArg, arguments)}}Copy the code

Reject () – the source code

/** reject() method * argument self: reject instance * parameter newValue: reject reason */
function reject(self, newValue) {
  self._state = 2 // reject _state = 2
  self._value = newValue // Assign the rejection reason to the _value attribute of the contract instance
  finale(self)
}
Copy the code
  • The process andresolve()Roughly the same will be about the term_stateand_valueAssign values separately and executefinale()methods

Finale () – Test code

  • Write a test by handfinale()Method, convenient to do stage test
/** Test the call () method * argument self: the call instance */
function finale(self) {
  console.log(self)
  if (self._state === 1) {
    console.log('resolve:' + self._value)
  } else if (self._state === 2) {
    console.log('reject:' + self._value)
  } else if (self._state === 3) {
    console.log('resolve value is Promise')}}Copy the code

New Promise – Phase test

  • createPromiseInstance, according to the executor functionThe callback type(resolve/reject) andResolve the type of value or rejection reasonPrint different results
/** Test: new Promise(()=>{}) * After the first resolve or reject is actually executed, subsequent resolve or reject will not be executed
new Promise((resolve, reject) = > {
  resolve(3) // 'resolve:3'
  /* self is the Promise {_state: 1, _handled: false, _value: 3, _deferreds: []} */
  reject(3) // 'reject:3', the reject value is a basic type
  /* self is the Promise {_state: 2, _handled: false, _value: 3, _deferreds: []} */
  resolve({ val: 3 }) // 'resolve:[object object]', resolve value is common object
  /* self is the Promise {_state: 1, _handled: false, _value: {val: 3}, _deferreds: []} */
  resolve(new Promise(() = > {})) // 'resolve value is Promise', resolve value is about instance
  /* self as Promise {_state: 3, _handled: false, _value: Promise {_state: 0, _handled: false, _value: undefined, _deferreds: [] }, _deferreds: [] } */
  resolve({
    Thenable {value: 3, then: [Function: then]}
    value: 3.then: function () {
      /* In the resolve() method, specify this inside the then method. If not specified, this refers to the global object window and this.value refers to undefined */
      console.log(this) // {value: 3, then: [Function: then]}, this refers to the solution value itself
      console.log(this.value) / / 3}})console.log('next coding... ') // 'next coding... ', will not affect subsequent code execution as long as no errors are thrown
  throw Error('error! ') // Throw an error
})
Copy the code

Summary of Implementation results

  • new PromiseInside the code, the executor function executes synchronously (immediately)
  • Once the executor function is resolved or rejected, subsequentresolveorrejectIt is not executed (and thrown errors are not caught), but does not affect other code
  • If the solution value isthenableObject, which will bethenMethods in thethisBind to the object and execute itthenmethods

As of the code in this section →