preface

Preface: The emergence of Promise has made great contributions to solve the problem of asynchrony and destroying hell. However, it is not enough to just stay at the level of being able to use it. Only by going deep into its essence can we realize its mystery. In this article, I’ll show you how to implement A Promise from 0 to 1 that complies with the Promise A+ specification, as well as some of the static approaches to promises.

Promise A+ specification see promisesaplus.com/

The realization of the Promise

A preliminary implementation of a Promise that can modify the state and preserve the result

  • Point 1: Executor type detection

As you can see from the following figure, TypeError is raised when executor is not of Function type.

  • Point 2: The Promise constructor requires passing in a function that takes arguments of two function types (resolve,reject) to change the state and value of the Promise. This function will be implemented immediately. The state of the Promise can only change from pendding to fulfilled and rejected once.

The concrete implementation is as follows

class Promise{
  constructor(executor) {
    // Cannot trust user input, parameter verification
    if(typeofexecutor ! = ='function') {throw new TypeError(`Promise resolver ${executor} is not a function`)}// Initialize the value
    this.value = null    / / final value
    this.reason = null    / / rejected
    this.state = 'pending'   / / state

    const resolve = value= > {
      // State can only be determined from pending => other
      if(this.state === 'pending') {this.state = Promise.FULFILLED
        this.value = value
      }
    }

    const reject = reason= > {
      if(this.state === 'pending') {this.state = Promise.REJECTED
        this.reason = reason
      }
    }
    // Execute the incoming executor immediately
    executor(resolve,reject)
  }
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'rejected'
Copy the code

Then method is preliminarily implemented

The then method can pass in two parameters of function types: onFulfilled and onRejected. These two parameters are the callback functions to be executed after the Promise state changes. The corresponding Promise value will be passed in during the execution.

  • Point 1: JudgeonFulfilledandonRejectedIs a function type, not a function type (or not passed, undefined) assigns a default callback to the chain callPass through the effect
  • Tip 2: Incoming callbacks can only be called when the state has changed (not when the state is pending)
class Promise{
  constructor(executor) {
    if(typeofexecutor ! = ='function') {throw new TypeError(`Promise resolver ${executor} is not a function`)}this.value = null  
    this.reason = null    
    this.state = Promise.PENDING   

    const resolve = value= > {
      if(this.state === Promise.PENDING){
        this.state = Promise.FULFILLED
        this.value = value
      }
    }

    const reject = reason= > {
      if(this.state === Promise.PENDING){
        this.state = Promise.REJECTED
        this.reason = reason
      }
    }
    executor(resolve,reject)
  }
  then(onFulfilled,onRejected){
    // Check parameters
    if(typeofonFulfilled ! = ='function'){
      onFulfilled = value= > value
    }
    if(typeofonRejected ! = ='function'){
      onRejected = reason= > {throw reason}
    }
    
    // Cannot be called before the state changes
    if(this.state === Promise.FULFILLED){
      onFulfilled(this.value)
    }
    // Cannot be called before the state changes
    if(this.state === Promise.REJECTED){
      onRejected(this.reason)
    }
  }
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'rejected'
Copy the code

Asynchronously change the Promise state

  • Point 1: The callback in the original Promise’s then method is executed asynchronously, as verified below, so the callback for the implemented promise is also executed asynchronously
console.log(1)
const p = new Promise((resolve,reject) = >{
    console.log(2)
    resolve()
})
p.then(value= >{
    console.log(4)},err= >{
    console.log(err)
})
console.log(3)
​
// the output of this code is 1,2,3,4 (original Promise)
// but in the previous section, the output is 1,2,4,3, that is, the callback in then is executed immediately
Copy the code
  • Point 2: In the constructorexecutorExecute outside addtry... catchTo solve theexecutorA problem that throws an exception in
  • Point 3: Add to THENDetermination of pending stateThe callback is stored in the array until the state changesexecutorExecute asynchrony inDelayed modification of state causes the callback function in then not to executeIn the case
class Promise{
  constructor(executor) {
    if(typeofexecutor ! = ='function') {throw new TypeError(`Promise resolver ${executor} is not a function`)}this.value = null
    this.reason = null
    this.state = Promise.PENDING
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = value= > {
      if(this.state === Promise.PENDING){
        this.state = Promise.FULFILLED
        this.value = value
        // Execute after state modification (callback stored for pending state)
        this.onFulfilledCallbacks.forEach(fn= >fn())
      }
    }

    const reject = reason= > {
      if(this.state === Promise.PENDING){
        this.state = Promise.REJECTED
        this.reason = reason
        // Execute after state modification (callback stored for pending state)
        this.onRejectedCallbacks.forEach(fn= >fn())
      }
    }
	Catch an exception in executor
    try {
      executor(resolve,reject)
    }catch (e) {
      reject(e)
    }

  }
  then(onFulfilled,onRejected){
    if(typeofonFulfilled ! = ='function'){
      onFulfilled = value= > value
    }
    if(typeofonRejected ! = ='function'){
      onRejected = reason= > {throw reason}
    }

    if(this.state === Promise.FULFILLED){
      // Execute the callback asynchronously
      setTimeout(() = >{
        onFulfilled(this.value)
      })
    }

    if(this.state === Promise.REJECTED){
        // Execute the callback asynchronously
      setTimeout(() = >{
        onRejected(this.reason)
      })
    }
	
    // Executor asynchronously changes the state. When executing THEN, the state is pending, and the callback is stored until the state is changed
    if(this.state === Promise.PENDING){
      this.onFulfilledCallbacks.push(() = >{
          // Execute the callback asynchronously
        setTimeout(() = >{
          onFulfilled(this.value)
        })
      })
      this.onRejectedCallbacks.push(() = >{
          // Execute the callback asynchronously
        setTimeout(() = >{
          onRejected(this.reason)
        })
      })
    }
  }
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'rejected'

module.exports = Promise
Copy the code

Chain call implementation

  • The then return value is a new Promise

  • Promise resolution process

    • ifx === promise2
    • ifx instanceof Promise
    • ifX is the object | | x is function

  • If the return value x of the THEN callback is a promise, and a promise is returned in resolve, then the outcome of promise2 is determined by the promise in resolve. (Status transfer)

class Promise{
  constructor(executor) {
    if(typeofexecutor ! = ='function') {throw new TypeError(`Promise resolver ${executor} is not a function`)}this.value = null
    this.reason = null
    this.state = Promise.PENDING
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = value= > {
      if(this.state === Promise.PENDING){
        this.state = Promise.FULFILLED
        this.value = value
        this.onFulfilledCallbacks.forEach(fn= >fn())
      }
    }

    const reject = reason= > {
      if(this.state === Promise.PENDING){
        this.state = Promise.REJECTED
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn= >fn())
      }
    }

    try {
      executor(resolve,reject)
    }catch (e) {
      reject(e)
    }

  }
  then(onFulfilled,onRejected){
    // If ondepressing is not a function, and promise1 performs successfully, promise2 must return the same final value (the state is depressing).
    if(typeofonFulfilled ! = ='function'){
      onFulfilled = value= > value
    }
    // If onRejected is not a function and promise1 rejects, promise2 must return the same rejected reason (the status is rejected).
    if(typeofonRejected ! = ='function'){
      onRejected = reason= > {throw reason}
    }
    
    // Return Promise
    let promise2 = new Promise((resolve,reject) = >{

      if(this.state === Promise.FULFILLED){
        setTimeout(() = >{
          // If onFulfilled or onRejected executes with an exception (e), then the returned promise must reject the implementation and return the rejection (e)
          try{
            // If a value is returned, the promise resolution process is executed
            const x = onFulfilled(this.value)
            Promise.resolvePromise(promise2, x, resolve, reject)
          }catch (e){
            reject(e)
          }
        })
      }

      if(this.state === Promise.REJECTED){
        setTimeout(() = >{
          try {
            const x = onRejected(this.reason)
            Promise.resolvePromise(promise2, x, resolve, reject)
          }catch (e) {
            reject(e)
          }
        })
      }

      if(this.state === Promise.PENDING){
        this.onFulfilledCallbacks.push(() = >{
          setTimeout(() = >{
            try {
              const x = onFulfilled(this.value)
              Promise.resolvePromise(promise2, x, resolve, reject)
            }catch (e){
              reject(e)
            }
          })
        })
        this.onRejectedCallbacks.push(() = >{
          setTimeout(() = >{
            try {
              const x = onRejected(this.reason)
              Promise.resolvePromise(promise2, x, resolve, reject)
            }catch (e){
              reject(e)
            }
          })
        })
      }
    })
    return promise2
  }
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'rejected'
Promise.resolvePromise = function(promise2,x,resolve,reject){

  // If the promise and x point to the same object, reject the promise as TypeError (forms a chain call)
  if(promise2 === x){
    reject(new TypeError('Chaining cycle detected for promise'))}{then(a,b){a(); then(a,b){a(); A ()}} is called multiple times and executed multiple times
  let flag = false
  // If the return value x is a promise type, then the promise type returned by then is the same as the result of the promise execution
  if(x instanceof Promise) {// When does the execution end? The executor execution in Promise (x) is finished when the callback in x. Teng is executed
    x.then(value= >{
      // It should not simply be resolved, because if x's resolve is a promise object, the outcome of promise2 is determined by that argument
      Promise.resolvePromise(promise2,value,resolve,reject)
    },reason= >{
      reject(reason)
    })
  }
  // If x is an object or a function, note that null is also of type object and therefore cannot be null
  else if(x ! = =null && (typeof x === 'object' || typeof x === 'function')) {// An exception may be thrown in the then method of an X object
    try{
      // The specification requires that we store x. teng in then to avoid repeating the value from the object
      const then = x.then
      // Determine if thenable object (then object)
      if(typeof then === 'function'){
        then.call(
            x,
            value= > {
              if(flag) return
              flag = true
              Promise.resolvePromise(promise2,value,resolve,reject)
            },
            reason= >{
              if(flag) return
              flag = true
              reject(reason)
            })
      }else{
        if(flag) return
        flag = true
        // If this object is not thenable, it is a normal object
        resolve(x)
      }
    }catch (e) {
      if(flag) return
      flag = true
      reject(e)
    }
  }
    // It is a normal value
  else{
    resolve(x)
  }
}

Copy the code

Static method implementation of Promise

The static method of a Promise simply changes the returned Promise state depending on when resolve and Reject are called. So let’s do it one by one.

Promise.all

The promise. all method returns all incoming promises as successful, an array of successful states and values, and a failure if any.

Promise.prototype.all = function (arr) {
  return new Promise((resolve, reject) = > {
    let res = [];
    // Iterate through the array, specifying then callbacks for each Promise object
    for (const promise of arr) {
      promise.then((data) = > {
        res.push(data);
        // If all promises are successful, change the state of the returned promise
        if(res.length === arr.length) resolve(res); }, reject); }}); };Copy the code

Promise.allSettled

Promise.allsettled the state of the returned Promise is changed only when all the incoming Promise states are changed, and the returned Promise holds an array of states and values for all the incoming promises.

Promise.prototype.allSettled = function (arr) {
  return new Promise((resolve, reject) = > {
    let res = [];
    // Iterate through the array, specifying then callbacks for each Promise object
    for (const promise of arr) {
      promise.then(
        (data) = > {
          res.push({ value: data, status: Promise.FULFILLED });
          // Change the state of the returned promise when all promise states have changed
          if (res.length === arr.length) resolve(res);
        },
        (err) = > {
          res.push({ value: err, status: Promise.REJECTED });
          // Change the state of the returned promise when all promise states have changed
          if(res.length === arr.length) reject(res); }); }}); };Copy the code

Promise.race

The promise. race method is to pass in an array of promises and return a Promise whose state depends on the first Promise object that changes state.

Promise.prototype.race = function (arr) {
  return new Promise((resolve, reject) = > {
    for (const promise of arr) {
      // When a promise state changes, change the returned promise state directlypromise.then(resolve,reject); }}); };Copy the code

Promise.any

The promise. any method is to pass in an array of promises and wait until one Promise succeeds before changing the state of the returned Promise to succeed. Otherwise, the state of the returned Promise is changed to failed only when all promises fail, and all reasons for failure are saved.

Promise.prototype.any = function (arr) {
  return new Promise((resolve, reject) = > {
    let res = [];
    // Iterate through the array, specifying then callbacks for each Promise object
    for (const promise of arr) {
      promise.then(resolve, (err) = > {
        res.push(err);
        // Change the state of the returned promise when all promises are in failed state
        if(res.length === arr.length) reject(res); }); }}); };Copy the code