Promise series

  1. Promise – Three questions of the soul
  2. Promise – Handwritten source code
  3. Promise – Fully functional

In the previous article, we implemented A simple Promise under the Promise A+ protocol, and the test passed. This time, we’ll complete some of the commonly used apis for Promise. Firstly, we divide THE API into static methods and prototype methods. The static methods include :resolve, Reject, All, Race, allSettled, and prototype methods: Catch, finally.

A static method

Promise.resolve(value)

The method stipulates:

  • Method returns a parsed Promise object for a given value.
  • If value is a non-Promise object, it is returned as an argument
  • If value is a Thenable Promise object, the final state of the flattened Promise is returned
  static resolve(value) {
    return new Promise((resolve, reject) = > {
      // If a non-Promise object is returned
      // In the case of a Promise object, we need to do something in the constructor's resolve method, passing the Promise object into then and passing our resolve,reject as the two callbacks to the THEN if the subsequent then completes the callback and calls resolve/rej Ect also informs the outer Promise whether it succeeds or fails
      resolve(value)
    })
  }
Copy the code

Promise.reject(reason)

The method returns a Promise object with a reason for the rejection

  static reject(reason) {
    return new Promise((resolve,reject) = > {
      reject(reason)
    })
  }
Copy the code

Promise.all(promise)

The method stipulates:

  • Accepts a traversable array, which can be a Promise object or a non-Promise object
  • Return a Promise instance
  • The Promise’s resolve is at the end of all successful callbacks, passing the non-deconstructed/deconstructed values as an array of values in order, and the array as the value of resolve
  • When Promise deconstructs a failure, it invokes the failure directly, Reject
static all(promises) {
  // Determine if a promise can be iterated, and throw an exception if not.return Promise((resolve,reject) = > {
    // hold all callback or unstructured values, and resolve
    const results = []
    let times = 0
    function resloveData(data, index) {
      Return values in the same order as those passed in
      results[index] = data
      times++
      if(times === promises.length) {
        resolve(results)
      }
    }

    for(let index = 0; index < promises.length; index++) {
      const _p = promises[index]
      if(isPromise(_p)) {
        // If it is an instance of Promise, then
        _p.then(value = {
          resloveData(value,index)
        }, reason= > {
          // If it fails, change the Promise state to failed
          reject(reason)
        })
      } else {
        // If it's not a Promise instance, add it to the returned array
        resloveData(_p,index)
      }
    }
  })
}

Copy the code

Promise.race(promise)

Method definition:

  • Accepts a traversable array, which can be a Promise object or a non-Promise object
  • Return a Promise instance
  • The Promise’s resolve is at the end of the first callback, returning the first callback value as a success or failure value
  • The race, as long as a value is taken, whether it is a win or a loss, or a non-deconstructed value, is returned directly
  static race(promises) {
    return new Promise((resolve, reject) = > {
      for (let index = 0; index < promises.length; index++) {
        const _p = promises[index]
        if (isPromise(_p)) {
          _p.then(
            (value) = > {
              resolve(value)
            },
            (reason) = > {
              reject(reason)
            }
          )
        } else {
          resolve(_p)
        }
      }
    })
  }
Copy the code

Promise.allSettled(promise)

Method definition:

  • Accepts a traversable array, which can be a Promise object or a non-Promise object
  • Return a Promise instance
  • The Promise’s resolve is passed in as the value of resolve at the end of all callbacks, with the undeconstructed/deconstructed values and loaded as a set of numbers
  • Both success and failure will return in success
  static allSettled(promises) {
    return new Promise((resolve, reject) = > {
      const results = []
      let times = 0
      function setData(index, value, reason, mStatus) {
        results[index] = {
          status: mStatus,
          value: value,
          reason: reason,
        }
        times++
        if (times === promises.length) {
          resolve(results)
        }
      }
      for (let index = 0; index < promises.length; index++) {
        const _p = promises[index]
        if (isPromise(_p)) {
          _p.then(
            (data) = > {
              setData(index, data, undefined.'fulfilled')},(reason) = > {
              setData(index, undefined, reason, 'rejected')})}else {
          setData(index, _p, undefined.'fulfilled'}}})}}Copy the code

Prototype chain method

The prototype we use most often is catch,finally, but note that the catch and finally here are different from our normal trycatch.

Promise.prototype.catch(err=>{})

Catch can be seen as then without onledcallback

catch(error) {
  return this.then(null,error)
}
Copy the code

Promise.prototype.finally(() => {})

Some special points about finally

  • The callback method has no value
  • The callback method must be executed
  • Return a Promise to continue the chain call
  finally(callback) {
    return this.then(data= > {
      return Promise.resolve(callback()).then(() = > data)
    }, reason= > {
      return Promise.resolve(callback()).then(() = > {
          throw reason
        })
    })
  }
Copy the code

The complete code

// Promise has three states: wait, success, and reject
const STATUS = {
  PENDING: 'PENDING'.FULFILLED: 'FULFILLED'.REJECTED: 'REJECTED',}class Promise {
  constructor(executor) {
    // Promise defaults to Pending
    this.status = STATUS.PENDING
    this.value = undefined // Store success values for subsequent chain calls
    this.reason = undefined // Store the cause of the failure to facilitate subsequent chain calls

    // If the state does not change and then is called, we need to store the successful/failed methods in then and wait until the state changes
    this.onFulfilledCallbacks = [] // Then's set of successful methods
    this.onRejectedCallbacks = [] // Set of failed methods for then

    // The user calls the resolve function to change the state of the current instance when executing the function correctly. The resolve function supports passing in an argument as a success value
    const resolve = (value) = > {
      // Determine if the value passed in is a Promise. If it is still a Promise, return the call to THEN directly, passing the success and failure callbacks of resolve/reject to then. If the Promise succeeds/fails, modify the current state of the Promise with the callback argument
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }

      // The promise state can be changed only in the wait state
      if (this.status === STATUS.PENDING) {
        // Change the instance status to successful state
        this.status = STATUS.FULFILLED
        // Accept the success value from the user
        this.value = value
        // After the state changes, the successful method set call then is initiated to implement asynchronous operation
        this.onFulfilledCallbacks.forEach((fn) = > fn())
      }
    }

    Reject is called by the user to modify the state of the current instance when an error occurs or an exception is thrown. Reject supports passing in a parameter as the cause of failure or rejection
    const reject = (reason) = > {
      // Promise can only be modified in the wait state
      if (this.status === STATUS.PENDING) {
        // Change the state of the instance to failed
        this.status = STATUS.REJECTED
        // Accept the failure cause sent by the user
        this.reason = reason
        // After the state changes, the successful method set call then is initiated to implement asynchronous operation
        this.onRejectedCallbacks.forEach((fn) = > fn())
      }
    }
    try {
      // New Promise() passes in a function that executes by default and takes two executable method arguments
      executor(resolve, reject)
    } catch (error) {
      // If the default function fails, the state is set to reject
      reject(error)
    }
  }

  /** * 1. Each Promise prototype has a THEN method on it that provides two executable method arguments *@param {*} Ondepressing successfully performs the function *@param {*} 2. The then method must return a promise. We do this by new a new promise *@returns New Promise() * 3. Then if the current successful/failed method is called, if an exception is thrown, then the next failed method will be used * + return the success/failure value x * + if x is an object * * + x is a Promise that obtains the THEN of X and continues to invoke the THEN until it succeeds in obtaining the normal object or base data type * + Successful and failed methods invoke the Promise in the promise2 THEN Resolve /reject methods * + The success or failure of the next THEN is determined by the success or failure of X * + If x is the base data type, the success method of the next THEN will be called (success or failure of the then method will be called) * If the Promise returned by the callback fails, then the next call will be resolved. If the Promise returned by the callback fails, then the next call will be resolved
  then(onFulfilled, onRejected) {
    // If onFulfilled/onRejected cannot obtain the current success/failure value to the next THEN
    // New promise((resolve,reject) =>{resolve(1)}).then().then().then(of=> {console.log(of)}) needs to be able to pass in the value of
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : (data) = > data
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : (e) = > {
            throw e
          }

    // Create a new promise2 and return it at the end of the method, then reaches the chain call, continuing then is actually calling the new promise instance
    const promise2 = new Promise((resolve, reject) = > {
      // When we call the then method, we call different pass-the-argument methods by determining the current Promise state
      // When the promise state is Fulfilled, the onFulfilled method is called and the success value is passed in as the parameter
      if (this.status === STATUS.FULFILLED) {
        // We need to handle promise2 and x, but promise2 is assigned after new, so we need to use an asynchronous macro/micro to get the promise
        setTimeout(() = > {
          try {
            const x = onFulfilled(this.value)
            // We use an external method to unify the processing here
            resolvePromise(x, promise2, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)}// When the promise status is REJECTED, call the onRejected method and pass the failure reason as an argument
      if (this.status === STATUS.REJECTED) {
        setTimeout(() = > {
          try {
            const x = onRejected(this.reason)
            // We use an external method to unify the processing here
            resolvePromise(x, promise2, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)}// When the state of a promise is PENDING, it is an asynchronous operation
      // Publish the resolve and reject methods in publish-subscribe mode and invoke them when subsequent state changes are complete
      if (this.status === STATUS.PENDING) {
        // Use decorator pattern/slicing to wrap the success/failure methods so that we can do other operations and get value/ Reason after state changes
        this.onFulfilledCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              const x = onFulfilled(this.value)
              // We use an external method to unify the processing here
              resolvePromise(x, promise2, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)})this.onRejectedCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              const x = onRejected(this.reason)
              // We use an external method to unify the processing here
              resolvePromise(x, promise2, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)})}})return promise2
  }

  /** * promise.prototype. Catch * call reject *@param {*} err
   * @returns* /
  catch(err) {
    return this.then(null, err)
  }

  /** * First understand that the finally function is not a try/catch function and will be executed anyway * Can return a Promise */
  finally(callback) {
    return this.then(
      (data) = > {
        The promise.resolve () method is called internally, and if the method is Promise, wait for it to complete
        return Promise.resolve(callback()).then(() = > data)
      },
      (err) = > {
        return Promise.resolve(callback()).then(() = > {
          throw err
        })
      }
    )
  }

  /********* Static method *******/
  /** * promise.resolve () implements * + if resolve is passed to a primitive value, the value will be passed to a Promise, and the Promise will support resolve(new) Promise()), i.e. * constructor(executor){*... * const resovle = function(value) { * ... * if (value instanceof Promise) { * return value.then(resolve, reject) * } * ... *} *... * * *}@param {*} value
   * @returns* /
  static resolve(value) {
    return new Promise((resolve, reject) = > {
      resolve(value)
    })
  }

  static reject(reason) {
    return new Promise((resolve, reject) = > {
      reject(reason)
    })
  }
  /** * Promise. All returns a Promise */
  static all(promises) {
    return new Promise((resolve, reject) = > {
      const result = []
      let times = 0
      function promiseData(index, val) {
        result[index] = val
        if (++times === promises.length) {
          resolve(result)
        }
      }
      for (let index = 0; index < promises.length; index++) {
        const _p = promises[index]
        // Check if it is Promise
        if (isPromise(_p)) {
          _p.then((data) = > {
            promiseData(index, data)
          }, reject)
        } else {
          promiseData(index, _p)
        }
      }
    })
  }

  static race(promises) {
    return new Promise((resolve, reject) = > {
      for (let index = 0; index < promises.length; index++) {
        const _p = promises[index]
        if (isPromise(_p)) {
          _p.then(
            (value) = > {
              resolve(value)
            },
            (reason) = > {
              reject(reason)
            }
          )
        } else {
          resolve(_p)
        }
      }
    })
  }

  static allSettled(promises) {
    return new Promise((resolve, reject) = > {

      const results = []
      let times = 0
      function setData(index, value, reason, mStatus) {
        results[index] = {
          status: mStatus,
          value: value,
          reason: reason,
        }
        times++
        if (times === promises.length) {
          resolve(results)
        }
      }

      for (let index = 0; index < promises.length; index++) {
        const _p = promises[index]
        if (isPromise(_p)) {
          _p.then(
            (data) = > {
              setData(index, data, undefined.'fulfilled')},(reason) = > {
              setData(index, undefined, reason, 'rejected')})}else {
          setData(index, _p, undefined.'fulfilled'}}})}}function isPromise(value) {
  return value && typeof value.then === 'function'
}

// Handle subsequent state changes to the Promise(promise2) returned in then
function resolvePromise(x, promise2, resolve, reject) {
  // If promise2 is passed in by itself
  if (x === promise2) {
    return reject(new TypeError('Repeat call'))}// Determine if the passed x is an object or method
  if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    // In order to be compatible with other promises, add an anti-repetition feature to prevent other promises from being called repeatedly
    let called
    Reject (x) if an exception is thrown
    try {
      // Get the then attribute of x
      const then = x.then
      // If then is function, we default x to Promise
      if (typeof then === 'function') {
        // Call the then method of x, and call the corresponding success and failure if x succeeds/fails internally
        then.call(
          x,
          (y) = > {
            if (called) return
            called = true
            // If y is still a Promise, then needs to be resolved again to know that it is not a Promise
            // resolve(y)
            resolvePromise(y, promise2, resolve, reject)
          },
          (r) = > {
            if (called) return
            called = true
            reject(r)
          }
        )
      } else {
        // If then is not a function, we default x to a non-promise object. Resolve (x)
        resolve(x)
      }
    } catch (error) {
      if (called) return
      called = true
      reject(error)
    }
  } else {
    Resolve (x) if the value passed is a primitive value type
    resolve(x)
  }
}

Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) = > {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

module.exports = Promise

Copy the code