Promise a prototype

Promise is really just a constructor.

It has only one argument, named after the Promise/A+ specification, and we call the Promise constructor argument executor, which is the argument of the function type.

This function “automatically” takes resolve and reject as arguments.

function Promise(executor) {}Copy the code

The Promise constructor returns an instance of a Promise object that has a then method.

In the then method, the caller can define two parameters, onfulfilled and onRejected, which are function type parameters.

Wherein, onfulfilled can obtain the value of the Promise object after resolve through the parameter, and onRejected can obtain the value of the Promise object after reject.

With this value, we handle the logic after the asynchronous operation has completed.

These are the basics of the Promise/A+ specification, so let’s move on to implementing promises.

  • Promise constructor
  • Add to the constructorthenPrototype method
function Promise(executor) {}Promise.prototype.then = function (onfulfilled, onrejected) {}Copy the code

Here is an example:

let promise1 = new Promise((resolve, reject) = > {
  resolve('data')
})

promise1.then(data= > {
  console.log(data)
})

let promise2 = new Promise((resolve, reject) = > {
  reject('error')
})

promise2.then(data= > {
  console.log(data)
}, error= > {
  console.log(error)
})
Copy the code

When the Promise constructor is called using the new keyword, the executor argument resolve is called at an appropriate time (usually at the end of an asynchronous operation) and the processed value is executed as the resolve function argument. This value can then be obtained in ondepressing, the first function parameter of the subsequent THEN method.

Similarly, when an error occurs, the executor argument reject is called and the error message is executed as a reject parameter, which is available in the onRejected, the second function argument to the subsequent THEN method.

Therefore, when we implement a Promise, we should have two variables that store a resolve value and a Reject value

You also need a variable to store the state, which is the state of the Promise instance (pending, depressing, Rejected).

Finally, you need to provide the resolve and reject methods, which need to be provided to the developer as executor arguments

As follows:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
  const self = this

  this.status = PENDING
  this.value = null
  this.reason = null

  function resolve(value) {
    self.value = value
  }

  function reject(reason) {
    self.reason = reason
  }

  executor(resolve, reject)
}

Promise.prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype) {
  onfulfilled(this.value)

  onrejected(this.reason)
}
Copy the code

In order to ensure the robustness of ondepressing and onRejected, we set the default value for them. The default value is a Function. Prototype.

Because the final call to resolve is made directly by the developer in an indeterminate (usually global) environment, we either need to save this in order to retrieve the Promise instance value in resolve, or we can use the arrow function to ensure that this is executed correctly

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
  this.status = PENDING
  this.value = null
  this.reason = null

  const resolve = value= > {
    this.value = value
  }

  const reject = reason= > {
    this.reason = reason
  }

  executor(resolve, reject)
}

Promise.prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype) {
  onfulfilled(this.value)

  onrejected(this.reason)
}
Copy the code

Why is then on the prototype of the Promise constructor, rather than inside it?

The logic of each Promise instance’s THEN method is consistent, and the instance can call the method from a stereotype (promise.propType) instead of creating a new THEN method for each instantiation to save memory

Promise improvement state

Determine the output of the following code:

let promise = new Promise((resolve, reject) = > {
  resolve('data')
  reject('error')
})

promise.then(data= > {
  console.log(data)
}, error= > {
  console.log(error)
})
Copy the code

Normally, the above code would only print data

We know that the state of a Promise instance can only change from Pending to fulfilled or from Pending to Rejected

Once a state change has been made, it cannot be changed or reversed again

The above code implementation obviously cannot meet this feature, and the last code output data and error, so the state needs to be judged and improved

As follows:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
  this.status = PENDING
  this.value = null
  this.reason = null

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

  const reject = reason= > {
    if (this.status === PENDING) {
      this.reason = reason
      this.status = REJECTED
    }
  }

  executor(resolve, reject)
}

Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data= > data
  onrejected = typeof onrejected === 'function' ? onrejected : error= > { throw error }

  if(this.status === FULFILLED)
    onfulfilled(this.value)

  if(this.status === REJECTED)
    onrejected(this.reason)
}
Copy the code

Judgments are added to the resolve and Reject methods, allowing only the state of a Promise instance to change from Pending to fulfilled or from pending to Rejected

The onfulfilled and onRejected parameters of promise.proptype. then are judged. When the argument is not a function type, the default function value needs to be assigned

The above sample code should then execute smoothly

However, promises were meant to address asynchrony, and our code was all executed synchronously, missing more important logic.

Promise asynchronous implementation

Let’s take a step-by-step look at the sample code below

let promise = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('data')},1000);
})

promise.then(data= > {
  console.log(data)
})
Copy the code

Normally, the above code should output data after 1s, but now the code does not output any information. Why is that?

The reason is simple, because our implementation logic is all synchronized. When the constructor of a Promise is instantiated, resolve is called in the setTimeout logic, meaning that the resolve method is called 1s later, changing the state of the Promise instance. However, the ondepressing in the then method is performed synchronously, and this. Status is still pending when the ondepressing is performed, and “ondepressing will be performed 1s later” is not fulfilled.

We should call the Ondepressing method at an appropriate time, which should be the time when the developer calls resolve. Then, we will first save the Ondepressing method sent in by the developer when the status is pending. Execute in the resolve method

As follows:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
  this.status = PENDING
  this.value = null
  this.reason = null

  this.onFulfilledFunc = Function.prototype
  this.onRejectedFunc = Function.prototype

  const resolve = value= > {
    if (this.status === PENDING) {
      this.value = value
      this.status = FULFILLED

      this.onFulfilledFunc(this.value)
    }
  }

  const reject = reason= > {
    if (this.status === PENDING) {
      this.reason = reason
      this.status = REJECTED

      this.onRejectedFunc(this.reason)
    }
  }

  executor(resolve, reject)
}

Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data= > data
  onrejected = typeof onrejected === 'function' ? onrejected : error= > { throw error }

  if (this.status === FULFILLED) {
    onfulfilled(this.value)
  }

  if (this.status === REJECTED) {
    onrejected(this.reason)
  }

  if (this.status === PENDING) {
    this.onFulfilledFunc = onfulfilled
    this.onRejectedFunc = onrejected
  }
}
Copy the code

Through testing, we found that the code we implemented can support asynchronous execution.

We know that Promise’s then method executes asynchronously. Look at another 🌰

let promise = new Promise((resolve, reject) = > {
  resolve('data')
})

promise.then(data= > {
  console.log(data)
})

console.log(1)
Copy the code

Normally, the above example would print 1 followed by data in that order

The code we implemented doesn’t take this into account and actually prints data first and then 1. Therefore, the resolve and Reject executions need to be placed on the task queue. Let’s just put them in setTimeout to ensure that they execute asynchronously. (This is not strictly done, as many Promise implementation libraries use MutationObserver to mimic nextTick to ensure that promises are microtasks.)

  const resolve = value= > {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }

    setTimeout(() = > {
      if (this.status === PENDING) {
        this.value = value
        this.status = FULFILLED

        this.onFulfilledFunc(this.value)
      }
    }, 0);
  }

  const reject = reason= > {
    setTimeout(() = > {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED

        this.onRejectedFunc(this.reason)
      }
    }, 0);
  }

  executor(resolve, reject)
Copy the code

This way, when executor(Resolve, Reject) is executed, the Promise task will be executed only in nextTick and will not block the synchronization task

At the same time, we added a statement to the resovle method to determine whether the value is a Promise instance

Here is the complete code so far:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
  this.status = PENDING
  this.value = null
  this.reason = null

  this.onFulfilledFunc = Function.prototype
  this.onRejectedFunc = Function.prototype

  const resolve = value= > {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }

    setTimeout(() = > {
      if (this.status === PENDING) {
        this.value = value
        this.status = FULFILLED

        this.onFulfilledFunc(this.value)
      }
    }, 0);
  }

  const reject = reason= > {
    setTimeout(() = > {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED

        this.onRejectedFunc(this.reason)
      }
    }, 0);
  }

  executor(resolve, reject)
}

Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data= > data
  onrejected = typeof onrejected === 'function' ? onrejected : error= > { throw error }

  if (this.status === FULFILLED) {
    onfulfilled(this.value)
  }

  if (this.status === REJECTED) {
    onrejected(this.reason)
  }

  if (this.status === PENDING) {
    this.onFulfilledFunc = onfulfilled
    this.onRejectedFunc = onrejected
  }
}
Copy the code

Promise complete details

Add multiple THEN methods before the state of the Promise instance changes

let promise = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('data')},1000);
})

promise.then(data= > {
  console.log(` 1:${data}`)
})
promise.then(data= > {
  console.log(` 2:${data}`)})Copy the code

Normally, the above code returns output

1: data
2: data
Copy the code

Our implementation only prints 2: data, because the onledFunc in the second THEN method overwrites the onledFunc in the first THEN method

To solve this problem, simply store all of the onimplemented ledFunc’s from the then methods in an array called onimplemented ray, and execute the onimplemented ledFunc’s in turn when the current Promise is decided. The same applies to onRejectedFunc

The modified code:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
  this.status = PENDING
  this.value = null
  this.reason = null

  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value= > {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }

    setTimeout(() = > {
      if (this.status === PENDING) {
        this.value = value
        this.status = FULFILLED

        this.onFulfilledArray.forEach(func= > {
          func(value)
        })
      }
    }, 0);
  }

  const reject = reason= > {
    setTimeout(() = > {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED

        this.onRejectedArray.forEach(func= > {
          func(reason)
        })
      }
    }, 0);
  }

  executor(resolve, reject)
}

Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data= > data
  onrejected = typeof onrejected === 'function' ? onrejected : error= > { throw error }

  if (this.status === FULFILLED) {
    onfulfilled(this.value)
  }

  if (this.status === REJECTED) {
    onrejected(this.reason)
  }

  if (this.status === PENDING) {
    this.onFulfilledArray.push(onfulfilled)
    this.onRejectedArray.push(onrejected)
  }
}
Copy the code

Another detail that needs to be worked out is that if something goes wrong in the constructor, it will start automatically. The Promise instance is in the rejected state, so we use try… The catch block wraps the executor

  try {
    executor(resolve, reject)
  } catch (e) {
    reject(e)
  }
Copy the code

Promise chain call

Let’s start with a question: Determine the output of the following code

const promise = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('andy')},1000);
})

promise.then(data= > {
  console.log(data)
  return `${data} next then`
}).then(data= > {
  console.log(data)
})
Copy the code

The above code prints Andy 1s later, followed by Andy next then

We can see that the THEN method of the Promise instance supports chain call. After outputing the value processed by resolve, if the new value is explicitly returned synchronously in the ondepressing function of the THEN method body, Then you will get the new value in the Ondepressing function of the new Promise instance then method

What if another Promise instance is returned in the onfulfilled function of the first THEN method body

Look at the following code:

const promise = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('andy')},1000);
})

promise.then(data= > {
  console.log(data)
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve(`${data} next then`)},2000);
  })
}).then(data= > {
  console.log(data)
})
Copy the code

This code will print Andy after 1s and then Andy next then after 2s

Therefore, the onfulfilled function and onRejected function of a Promise instance then method support returning a Promise instance again, and also support returning the normal value of a non-Promise instance

Moreover, the normal value of the returned Promise instance or non-Promise instance will be passed to the onfulfilled function or onRejected function of the next THEN method

Preliminary implementation of chain call

In order to support chain calls of the THEN method, each OF the ONfulfilled and onRejected functions of the THEN method should return a Promise instance

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
  this.status = PENDING
  this.value = null
  this.reason = null

  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value= > {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }

    setTimeout(() = > {
      if (this.status === PENDING) {
        this.value = value
        this.status = FULFILLED

        this.onFulfilledArray.forEach(func= > {
          func(value)
        })
      }
    }, 0);
  }

  const reject = reason= > {
    setTimeout(() = > {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED

        this.onRejectedArray.forEach(func= > {
          func(reason)
        })
      }
    }, 0);
  }

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

Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data= > data
  onrejected = typeof onrejected === 'function' ? onrejected : error= > { throw error }

  let promise2

  if (this.status === FULFILLED) {
    return promise2 = new Promise((resolve, reject) = > {
      setTimeout(() = > {
        try {
          let result = onfulfilled(this.value)
          resolve(result)
        } catch (e) {
          reject(e)
        }
      }, 0); })}if (this.status === REJECTED) {
    return promise2 = new Promise((resolve, reject) = > {
      setTimeout(() = > {
        try {
          let result = onrejected(this.reason)
          resolve(result)
        } catch (e) {
          reject(e)
        }
      }, 0); })}if (this.status === PENDING) {
    return promise2 = new Promise((resolve, reject) = > {
      this.onFulfilledArray.push(() = > {
        try {
          let result = onfulfilled(this.value)
          resolve(result)
        } catch (e) {
          reject(e)
        }
      })

      this.onRejectedArray.push(() = > {
        try {
          let result = onrejected(this.reason)
          resolve(result)
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}
Copy the code

The most important thing to understand here is the logic in the this.status === PENDING decision branch, which is also the most difficult to understand. When a Promise instance calls its THEN method, it should return a Promise instance that returns the promise2 returned in the this.status === PENDING judgment branch. So when is promise2 going to be resolved? This should be done after the asynchronous processing is completed, when the functions in the ondefaulledarray or onRejectedArray array are executed in sequence

What should the functions in the ononledarray and onRejectedArray arrays do?

Switch the state of promisE2 and initiate a resolution

Perfect implementation of chain call

We continue implementing the THEN method to explicitly return a Promise instance.

As follows:

const promise = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('andy')},1000);
})

promise.then(data= > {
  console.log(data)

  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve(`${data} next then`)},2000);
  })
}).then(data= > {
  console.log(data)
})
Copy the code

Based on the first onfulfilled function and onRejected function returning a normal value, it is not difficult to implement this onfulfilled function and onRejected function returning a Promise instance. This is a pity (this. Value) statement and let result = onfulfilled(this. Reason) statement. Change the variable result from a normal value to a Promise instance.

The variable result can be either a common value or a Promise instance, so we abstract the resulvePromise method for unified processing. Code that has been modified to an existing implementation. As follows:

const resolvePromise = (promise2, result, resolve, reject) = >{}Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data= > data
  onrejected = typeof onrejected === 'function' ? onrejected : error= > { throw error }

  let promise2

  if (this.status === FULFILLED) {
    return promise2 = new Promise((resolve, reject) = > {
      setTimeout(() = > {
        try {
          let result = onfulfilled(this.value)
          resolvePromise(promise2, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }, 0); })}if (this.status === REJECTED) {
    return promise2 = new Promise((resolve, reject) = > {
      setTimeout(() = > {
        try {
          let result = onrejected(this.reason)
          resolvePromise(promise2, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }, 0); })}if (this.status === PENDING) {
    return promise2 = new Promise((resolve, reject) = > {
      this.onFulfilledArray.push(() = > {
        try {
          let result = onfulfilled(this.value)
          resolvePromise(promise2, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })

      this.onRejectedArray.push(() = > {
        try {
          let result = onrejected(this.reason)
          resolvePromise(promise2, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}
Copy the code

The task now is to implement the resolvePromise function, which takes four parameters:

  • promise2: returns an instance of Promise
  • result:onfulfilled ζˆ– onrejectedThe return value of the function
  • resolve:promise2 ηš„ resolvemethods
  • reject:promise2 ηš„ rejectmethods
const resolvePromise = (promise2, result, resolve, reject) = > {
  // Execute reject when result and promise are equal, i.e., when onfulfilled returns promise2
  if (result === promise2) {
    reject(new TypeError('error due to circular reference'))}// Whether you have performed onfulfilled or onRejected
  let consumed = false
  let thenable

  if (result instanceof Promise) {
    if (result.status === PENDING) {
      result.then((data) = > {
        resolvePromise(promise2, data, resolve, reject)
      }, reject)
    } else {
      result.then(resolve, reject)
    }
    return
  }

  let isComplexResult = (target= > typeof target === 'function' || typeof target === 'object') && target ! = =null

  // If the return is of type Promise
  if (isComplexResult(result)) {
    try {
      thenable = result.then

      // Determine if the return value is of type Promise
      if (typeof thenable === 'function') {
        thenable.call(result, function (data) {
          if (consumed) {
            return
          }
          consumed = true

          return resolvePromise(promise2, data, resolve, reject)
        }, function (error) {
            if (consumed) {
              return
            }
            consumed = true

            return reject(error)
        })
      } else {
        resolve(result)
      }
    } catch (e) {
      if (consumed) {
        return
      }
      consumed = true

      return reject(e)
    }
  } else {
    resolve(result)
  }
}
Copy the code

The first step in the resolvePromise method is to handle an “infinite loop” and throw an error if one occurs

How do we understand this? As the Promise specification points out, this is what happens when an “infinite loop” occurs:

const promise = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('andy')},1000);
})

promise.then(onfulfilled = data= > {
  console.log(data)
  return onfulfilled(data)
}).then(data= > {
  console.log(data)
})
Copy the code

The ondepressing function returns result: If result is not a Promise instance, object, or function, but an ordinary value (isCompexResult function is used to judge this), then promise2 will be resolved directly

For the result returned by the ondepressing function: If result contains the THEN method, then we call that method thenable, indicating that result is an instance of Promise. When executing the then method of that instance, The return result can also be a Promise instance type or a plain value, so resolvePromise is also recursively called.

As follows:

const promise = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('andy')},1000);
})

promise.then(data= > {
  console.log(data)
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve(`${data} next then`)},2000);
  }).then(data= > {
    return new Promise((resolve, reject) = > {
      setTimeout(() = > {
        resolve(`${data} next then`)},2000);
    })
  })
}).then(data= > {
  console.log(data)
})
Copy the code

The above code will output Andy after 1s and Andy next then next then

Now the full code:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
  this.status = PENDING
  this.value = null
  this.reason = null

  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value= > {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }

    setTimeout(() = > {
      if (this.status === PENDING) {
        this.value = value
        this.status = FULFILLED

        this.onFulfilledArray.forEach(func= > {
          func(value)
        })
      }
    }, 0);
  }

  const reject = reason= > {
    setTimeout(() = > {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED

        this.onRejectedArray.forEach(func= > {
          func(reason)
        })
      }
    }, 0);
  }

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

const resolvePromise = (promise2, result, resolve, reject) = > {
  // Execute reject when result and promise are equal, i.e., when onfulfilled returns promise2
  if (result === promise2) {
    reject(new TypeError('error due to circular reference'))}// Whether you have performed onfulfilled or onRejected
  let consumed = false
  let thenable

  if (result instanceof Promise) {
    if (result.status === PENDING) {
      result.then((data) = > {
        resolvePromise(promise2, data, resolve, reject)
      }, reject)
    } else {
      result.then(resolve, reject)
    }
    return
  }

  let isComplexResult = (target= > typeof target === 'function' || typeof target === 'object') && target ! = =null

  // If the return is of type Promise
  if (isComplexResult(result)) {
    try {
      thenable = result.then

      // Determine if the return value is of type Promise
      if (typeof thenable === 'function') {
        thenable.call(result, function (data) {
          if (consumed) {
            return
          }
          consumed = true

          return resolvePromise(promise2, data, resolve, reject)
        }, function (error) {
            if (consumed) {
              return
            }
            consumed = true

            return reject(error)
        })
      } else {
        resolve(result)
      }
    } catch (e) {
      if (consumed) {
        return
      }
      consumed = true

      return reject(e)
    }
  } else {
    resolve(result)
  }
}

Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data= > data
  onrejected = typeof onrejected === 'function' ? onrejected : error= > { throw error }

  let promise2

  if (this.status === FULFILLED) {
    return promise2 = new Promise((resolve, reject) = > {
      setTimeout(() = > {
        try {
          let result = onfulfilled(this.value)
          resolvePromise(promise2, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }, 0); })}if (this.status === REJECTED) {
    return promise2 = new Promise((resolve, reject) = > {
      setTimeout(() = > {
        try {
          let result = onrejected(this.reason)
          resolvePromise(promise2, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }, 0); })}if (this.status === PENDING) {
    return promise2 = new Promise((resolve, reject) = > {
      this.onFulfilledArray.push(() = > {
        try {
          let result = onfulfilled(this.value)
          resolvePromise(promise2, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })

      this.onRejectedArray.push(() = > {
        try {
          let result = onrejected(this.reason)
          resolvePromise(promise2, result, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}
Copy the code

Promise penetration

At this point, with the exception of static methods, our Promise implementation is almost 95% complete

Why not 100%? There is one detail left to complete, so let’s look at the following code to determine the output

const promise = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('andy')},1000);
})

promise.then(null).then(data= > {
  console.log(data)
})
Copy the code

The above code will print Andy 1s later.

This is Promise penetration: passing a non-functional value as an argument to a THEN () function actually resolves to THEN (NULL), in which case the resolution result of the previous Promise object “passes” into the argument of the next THEN method

How do I implement Promise penetration

It’s actually very simple, and it’s already implemented. In the implementation of the THEN () method, we have added the following judgment to the onfulfilled and onRejected functions:

Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data= > data
  onrejected = typeof onrejected === 'function' ? onrejected : error= > { throw error }
  
  // ...
}
Copy the code

If ondepressing is not a function type, a default value is given, which is the function that returns its parameters. The same goes for the onreject function. This code implements the logic of “penetration”

Promise static methods and other methods

  • Promise.proptype.catch
  • Promise.resolve
  • Promise.reject
  • Promise.all
  • Promise.race

Promise. Prototype. The catch

Promise.prototype.catch can be used to catch exceptions. Usage:

const promise = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    reject('error')},1000);
})

promise.then(data= > {
  console.log(data)
}).catch(error= > {
  console.log(error)
})
Copy the code

The above code will print error after 1s

In this case, it is equivalent to the following code

Promise.prototype.catch = function (catchFunc) {
  return this.then(null, catchFunc)
}
Copy the code

Because we know that the second argument to the then() method also catches exceptions, we can implement promise.prototype.catch relatively easily with this feature

Promise. Resolve to achieve

The MDN: promise.resolve (value) method returns a Promise instance object resolved with the given value

🌰 :

Promise.resolve('data').then(data= > {
  console.log(data)
})
console.log(1)
Copy the code

The code above prints 1 and then data

Implementation:

Promise.resolve = function (value) {
  return new Promise((resolve, reject) = > {
    resolve(value)
  })
}
Copy the code

Implement promise.reject (reason)

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

Promise. All implementations

MDN: Promise.all(iterable) returns an instance of Promise, This instance is’ resolved ‘for all Promise instances in the iterable argument or completes the resolve call if the Promise instance is not included in the argument; If the Promise instance in the parameter has a failure (Rejected), the instance callback fails (Reject) for the same reason that the first Promise instance failed

🌰 :

let promise1 = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('andy')},1000)})let promise2 = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('andy')},1000)})Promise.all([promise1, promise2]).then(data= > {
  console.log(data)
})
Copy the code

The above code will print [” Andy “, “Andy “] after 1s.

Implementation:

Promise.all = (promiseArray) = > {
  if (!Array.isArray(promiseArray)) {
    throw new TypeError('The arguments should be an array! ')}return new Promise((resolve, reject) = > {
    try {
      let resultArray = []
      const len = promiseArray.length

      if (len === 0) {
        return resolve(resultArray)
      }

      let count = 0
      for (let i = 0; i < len; i++) {Promise.resolve(promiseArray[i]).then(data= > {
          count++
          resultArray[i] = data

          if (count === len) {
            resolve(resultArray)
          }
        }, reject)
      }
    } catch (e) {
      reject(e)
    }
  })
}
Copy the code

If the parameter is not an array type, throw an error. Promise.all returns a Promise instance that will be resolved after all the Promise instances in the promiseArray have been resolved. The result is an array, This array holds resolution values for all Promise instances in the promiseArray

Promise. Race

🌰 :

let promise1 = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('andy1')},1000)})let promise2 = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('andy2')},2000)})Promise.race([promise1, promise2]).then(data= > {
  console.log(data)
})
Copy the code

This code will print andy1 after 1s

Implementation:

Promise.race = (promiseArray) = > {
  if (!Array.isArray(promiseArray)) {
    throw new TypeError('The arguments should be an array! ')}return new Promise((resolve, reject) = > {
    try {
      const len = promiseArray.length

      if (len === 0) {
        return resolve()
      }

      for (let p of promiseArray) {
        Promise.resolve(p).then(resolve, reject)
      }
    } catch (e) {
      reject(e)
    }
  })
}
Copy the code

Synchronously execute the THEN methods of all Promise instances in the promiseArray array, and the first resolve example directly triggers a new Promise instance

conclusion

Promise/A+

  1. PromiseOnce the state of is changed, it cannot be changed. (see 3.1)
  2. .then ε’Œ .catchWill return a new onePromise.
  3. catchUpper-level errors can be caught no matter where it is connected. (see 3.2)
  4. inPromise, returns any notpromiseIs wrapped around the value ofpromiseObject, for examplereturn 2Will be packaged asreturn Promise.resolve(2) 。
  5. Promise ηš„ .thenor.catchCan be called multiple times when ifPromiseOnce the internal state changes and there is a value, then each subsequent call.thenor.catch“Will directly get the value. (see 3.5)
  6. .thenor.catch δΈ­ returnaerrorThe object does not throw an error, so it is not followed.catchCapture. (see 3.6)
  7. .then ζˆ– .catchThe returned value cannot bepromiseItself, otherwise it will cause an endless loop. (see 3.7)
  8. .thenor.catchThe argument to is expected to be a function. Passing in a non-function will result in value penetration. (see 3.8)
  9. .thenMethods can take two arguments, the first to handle the successful function, the second to handle the failed function, and at some point you can saycatch 是 .thenA handy way to write the second parameter. (see 3.9)
  10. .finallyMethod also returns onePromiseIn hisPromiseAt the end of the day, whatever the result isresolvedorrejected, will execute the callback function inside.
  11. .finally()Methods no matterPromiseHow will the final state of the object be executed
  12. .finally()The callback function of the method does not take any arguments, which means that you are in.finally()There’s no way to knowPromiseThe final state of
  13. What it ultimately returns isThe last Promise object valueException is returned if an exception is thrownPromiseobject
  14. Promise.all()Is used to receive a set of asynchronous tasks, execute the asynchronous tasks in parallel, and wait until all the asynchronous operations are complete before executing the callback
  15. .race()The function also receives a set of asynchronous tasks and executes them in parallel, preserving only the results of the first completed asynchronous operation. The other methods are still executed, but the results are discarded
  16. Promise.all().then()The order sum of the arrays in the resultPromise.all()The received arrays are in the same order
  17. all ε’Œ raceIf there is an asynchronous task in the array that throws an exception, only the first error thrown will be caught, and will bethenThe second argument to or aftercatchCapture; This does not affect the execution of other asynchronous tasks in the array