An overview of the

Usually use Promise to solve practical problems more, today we come to understand how to realize the Promise internally to chain call, Promise of different API implementation, in order to deepen the understanding of Promise.

A simple Promise

Here’s an example:

Const promise1 = new Promise((resolve, reject) => {resolve(' succeed '); Reject (' reject '); } // promise1.then(value => { console.log('sucess:', value) }, reason => { console.log('failed:', reason) })Copy the code

Run result: success: successful. If: swap resolve and reject:

{reject(' reject '); Resolve (' succeed '); }Copy the code

Running result: failed: indicates a failure.

A Promise instantiates an object through a constructor and then processes the returned result through the then method on the instance object. Meanwhile, the Promise /A+ specification states:

A promise is an object or function with a then method that behaves in accordance with this specification; The current state of a Promise must be one of the following three states: Pending, Fulfilled and Rejected.

Const PENDING = 'PENDING' // const REJECTED = 'REJECTED' // fail class MyPromise { // Promise is a class that, when executed, passes an executor that executes immediately; Constructor (executor) {} /** * Promise has three states. This is a big pity, which is a pity, which is a pity, which is a pity, which is a pity, which is a pity, which is a pity, which is a pity, which is a pity, which is a pity, which is a pity, which is a pity, which is a pity, which is a pity. **/ resolvedValue = null /** ** When the promise status is REJECTED, you need to cache the reason. **/ rejectedReason = null /** * Instance method -reject ***/ reject = reason => {} /** * type: Prototype method -reject ***/ then(ondepressing, onRejected) {}} module. Exports = {MyPromise}Copy the code

Executed immediately

When we instantiate a Promise, the constructor immediately calls executor, passing resolve and reject as arguments:

    constructor (executor) {
        executor(this.resolve, this.reject)
    }
Copy the code

Since there may be exceptions in the executor function, we need to catch exceptions:

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

immutable

According to the PROMISE /A+ specification, after the Promise object has changed from Pending to Fulfilled or Rejected state, the state cannot be changed again and the final value cannot be changed either. Therefore, we determine in the resolve and reject callbacks that the state can only be changed pending:

Resolve = value => {if(this.status! // constructor init this. Status = very depressing} // fail to accept = reason => {if(this. Status! == PENDING) return // constructor init params this.status = REJECTED }Copy the code

Since MyPromise internally needs to execute the final value, we need to cache either the success value or the failure reason:

Resolve = value => {... This. ResolvedValue = value} // Reject = reason => {... this.rejectedReason = reason }Copy the code

Then achieve

When the state of a Promise changes, the then callback is triggered, whether it succeeds or fails. Therefore, the implementation of THEN is simple, which is to call different functions that handle the end value depending on the state.

then(onFulfilled, onRejected) {
    if(this.status === FULFILLED) {
        onFulfilled(this.resolvedValue)
    } else if(this.status === REJECTED) {
        onRejected(this.rejectedReason)
    }
}
Copy the code

In the specification, ondepressing and onRejected are optional, and the parameters are described as follows:

This is a pity. If the argument is not a function, it is internally replaced with (x) => x, the function that returns the promise final result as is

OnRejected: Optional: If this parameter is not a function, it is internally replaced with a “Thrower” function

So, we need to be compatible with both:

    then(onFulfilled, onRejected) {
      onFulfilled = typeof onRejected === 'function' ? onFulfilled : x => x
      onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
    
    
      if(this.status === FULFILLED) {
          onFulfilled(this.resolvedValue)
      } else if(this.status === REJECTED) {
          onRejected(this.rejectedReason)
      }
    }

Copy the code

Above, completing a basic function of MyPromise, here’s a test case:

Const promise1 = new Promise((resolve, reject) => {resolve(' succeed '); // 'sucess: reject(' reject '); // 'failed: // No Print setTimeout(reject, 500, reject, 500) Promise. log(value => {console.log('sucess:', value)}, reason => {console.log('failed:', reason) })Copy the code

Running result: Synchronous execution result is normal, asynchronous execution, no output;

Analysis reason: Because the executor is executed immediately and the resolve/ Reject method is executed asynchronously in setTimeout, the status inside MyPromise is PENDING when the THEN method is executed. When setTimeout ends, the callback function in the then method cannot be triggered.

Then supports asynchronous

Reference/Quote: Portal – Write promises from scratch

When executing then, if the state is still PENDING, the callback function is stored in an array. When the state changes, the callback function is retrieved from the array. So let’s define the variables in the Promise:

class MyPromise { ... /** * The same promise can be called multiple times. If resolve is called asynchronously, This is a big ondepressing **/ asyncResolveCallbackbs = [] /** * The same promise can be called several times. OnRejected **/ asyncRejectCallbacks = []... }Copy the code

When then executes, if it is still PENDING, instead of executing the callback function immediately, we store it:

then(onFulfilled, onRejected) {
  onFulfilled = typeof onRejected === 'function' ? onFulfilled : x => x
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }


  if(this.status === FULFILLED) {
      onFulfilled(this.resolvedValue)
  } else if(this.status === REJECTED) {
      onRejected(this.rejectedReason)
  } else {
      this.asyncResolveCallbackbs.push(onFulfilled)
      this.asyncRejectCallbacks.push(onRejected)
  }
}

Copy the code

Once stored, resolve or reject can be called asynchronously:

resolve = value => {
    ...
    // async resolve
    while(this.asyncResolveCallbackbs.length) {
        const curAsyncResolveCb = this.asyncResolveCallbackbs.shift()

        curAsyncResolveCb(this.resolvedValue)
    }
}

reject = reason => {
    ...

    // async reject
    while(this.asyncRejectCallbacks.length) {
        const curAsyncRejectCb = this.asyncRejectCallbacks.shift()

        curAsyncRejectCb(this.rejectedReason)
    }

}
Copy the code

Run the above example and the result is fine.

Then supports chained calls

Reference/Quote/elaborate: Portal – Write Promises from scratch

Then methods make chained calls, and each then method returns a new Promise object. Meanwhile, the later THEN method receives the return value of the callback function of the previous THEN method.

Rewrite the THEN method to return a new Promise object, no matter what the THEN does:

then(onFulfilled, onRejected) { // callback is null onFulfilled = onFulfilled ? onFulfilled : x => x onRejected = onRejected ? onRejected : Reason => {throw Reason} const newThenPromise = new MyPromise((resolve, reject) => { SetTimeout (() => {// catch the exception thrown in the then callback, And pass to the next then method const catchThenException = (callbackFunc, CacheValue) => {try {const callbackFuncValue = callbackFunc(cacheValue) // Chain call, last return value may be a normal value, It may also be a promise object callbackFuncValue && resolvePromise(callbackFuncValue, newThenPromise, resolve, reject) } catch (e) { reject(e) } } // console.log('____this.status', this.status) if(this.status === FULFILLED) { // onFulfilled(this.resolvedValue) catchThenException(onFulfilled, this.resolvedValue) } else if(this.status === REJECTED) { // onRejected(this.rejectedReason) catchThenException(onRejected, Enclosing rejectedReason)} else {/ / synchronous asynchronous / / this. | asyncResolveCallbackbs. Push (onFulfilled) / / This. AsyncRejectCallbacks. Push (onRejected) / / synchronous asynchronous | | chain call = > collect success callback enclosing asyncResolveCallbackbs. Push (() = > { catchThenException(onFulfilled, Enclosing resolvedValue)}) / / synchronous asynchronous | | chain call = > collect failure callback enclosing asyncRejectCallbacks. Push (() = > {catchThenException (onRejected,  this.rejectedReason) }) } }, 0) }) return newThenPromise }Copy the code

Catch method implementation

A catch method is a prototype chain method used to catch a reject exception in a previous function run.

The catch() method returns a Promise and handles the rejection. It behaves the same as calling promise.prototype. then(undefined, onRejected).

So the catch method is a wrapper around the THEN method:

catch(onRejected) {
    return this.then(undefined, onRejected)
}
Copy the code

Promise.all method implementation

The all method is a static method that returns a Promise, the description of all in MDN:

  • If the argument passed is an empty iterable, return a Promise that is already resolved.
  • If the parameters passed in do not contain any promises, an asynchronous completion is returned.
  • In any case, the result of the completion state returned by promise.all is an array containing the values of all the passed iteration parameter objects (including non-Promise values)
  • If a promise is passed in with a failed promise, promise. all asynchronously gives the failed result to the failed state callback, regardless of whether the other promise is completed.
Static all(array) {const resolveResult = [] return new MyPromise((resolve, resolve, resolveResult) reject) => { const setResolveResult = (index, Value) => {resolveResult[index] = value const effectiveResult = resolveResult. Filter (effctive => { Effectiveresult. length === array.length && resolve(resolveResult)}  for(let i = 0; i<array.length; I ++) {const curArgument = array[I] If (curArgument instanceof MyPromise) {curArgument. Then (value => setResolveResult(I, value), reject) } else { setResolveResult(i, curArgument) } } }) }Copy the code

Promise.all() is better for relying on each other or ending immediately in either reject.

Promise.allsettled method implemented

The allSettled method is a static method that returns a Promise, the description of allSettled in MDN:

The promise.allSettled () method returns a Promise after all the given promises have fulfilled or rejected, with an array of objects, each representing the corresponding Promise result.

static all(array) { const resolveResult = [] return new MyPromise((resolve, reject) => { const setEachResult = (index, Value) => {resolveResult[index] = value const effectiveResult = resolveResult. Filter (effctive => { Effectiveresult. length === array.length && resolve(resolveResult)}  for (let i = 0; i < array.length; i++) { const curArgument = array[i] if(curArgument instanceof MyPromise) { curArgument.then(value => { setEachResult(i, value) }, reason => { setEachResult(i, reason) }) } else { setEachResult(i, curArgument) } } }) }Copy the code

AllSettled is commonly used when there are multiple asynchronous tasks that are not dependent on each other to complete successfully, or when you always want to know the outcome of each promise.

An implementation of the any method

The any method is a static method that returns a Promise, as described in MDN:

Promise.any() receives a Promise iterable and returns the Promise that succeeded as soon as one of the promises succeeds. If none of the promises in the iterable succeed (i.e. all Promises fail/reject), return an instance of the failed promise and AggregateError type, which is a subclass of Error and is used to group single errors together. Essentially, this method is the opposite of promise.all ().

static any(array) { return new MyPromise((resolve, reject) => { let setEffectvalue = null; let rejectCount = 0 const effctivePromise = array.filter(cur => { if(cur instanceof MyPromise) return cur }) // Passing in non-PROMISE arguments, If (array.length === 0) {reject('No Arguments is Effctive') return} else if(effctivepromise.length === 0) { resolve(array) return } for (let i = 0; i < effctivePromise.length; If (setEffectvalue) break; if(setEffectvalue) break; if(setEffectvalue) break; effctivePromise[i].then(resolveValue => { setEffectvalue = resolveValue resolve(setEffectvalue) }, Reason => {rejectCount++ // if all Promise execution fails, RejectCount === effctivePromise.length && Reject ('AggregateError: No Promise in Promise.any was resolved') }) } }) }Copy the code

This method is used to return the first successful promise. This method terminates as soon as one promise succeeds, rather than waiting for all the other promises to complete.

Implementation of the RACE method

The RACE method is a static method that returns a Promise, described in MDN as race:

The race function returns a Promise that will be completed in the same way as the first Promise passed. It may be a high degree of completion or rejection, depending on which of the two is the first.

If the passed iteration is empty, the returned promise will wait forever.

If the iteration contains one or more non-promise values and/or resolved/rejected promises, promise.race resolves to the first value found in the iteration.

Static race(array) {let isEffectValue = null return new Promise((resolve, resolve) reject) => { if(array.length === 0) return for (let i = 0; i < array.length; i++) { if(isEffectValue) break const cur = array[i] if(cur instanceof MyPromise) { cur.then(value => { isEffectValue = value resolve(isEffectValue) }, reason => { isEffectValue = reason reject(isEffectValue) }) } } }) }Copy the code

Implementation of the Resolve method

The resolve method is a static method that returns a Promise, the MDN description of race:

The static method promise. resolve returns a resolved Promise object. If the parameter itself is a Promise object, the Promise object is returned directly.

static resolve(value) {
    if(value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
}
Copy the code

Implementation of Finaly method

A Finaly method is a prototype chain method that returns a Promise, as described by Finaly in MDN:

The finally() method returns a Promise. At the end of the promise, the specified callback function will be executed, whether the result is fulfilled or Rejected. This provides a way for code to be executed after a Promise is successfully completed or not.

Grammar:

p.finally(onFinally)

p.finally(function() {
   // doSomething
})
Copy the code

Description:

  • When you call an inline function, you don’t need to declare the function multiple times or create a variable for the function to hold it.
  • Since there is no way to know the final state of the promise, no arguments are taken in the finally callback function, which is only used when the final result is to be executed regardless.
  • Finaly can set to return a method that calls the then method after execution of the method.
finaly(callback) {
    return this.then(value => {
        return MyPromise.resolve(callback()).then(() => value)
    }, reason => {
        return MyPromise.resolve(callback()).then(() => { throw reason })
    })
}
Copy the code

reference

  • Promises/A + specification
  • Write promises from scratch
  • MDN Promise