This article is mainly to explore the realization principle of Promise, lead us to realize a Promise step by step, do not explain its use, if the reader still do not understand the use of Promise, you can see Teacher Ruan Yifeng ES6 Promise tutorial.

Next, take you step by step to fulfill a Promise

1. PromiseThe basic structure

new Promise((resolve, reject) = > {
  setTimeout((a)= > {
    resolve('FULFILLED')},1000)})Copy the code

The constructor Promise must accept as an argument a function called handle, which in turn contains resolve and reject, two functions.

Define a method for determining whether a variable is a function, which will be used later

// check whether the variable is function
const isFunction = variable= > typeof variable === 'function'
Copy the code

First, we define a Class named MyPromise that takes a function, Handle, as an argument

class MyPromise {
  constructor (handle) {
    if(! isFunction(handle)) {throw new Error('MyPromise must accept a function as a parameter')}}}Copy the code

Looked down again

2. PromiseStatus and value

Promise objects have the following three states:

  • Pending

  • This is a big pity.

  • Rejected(failed)

The state can only change from Pending to depressing or from Pending to Rejected. After the state changes, it will not change again and will always remain in this state.

The Promise value is the value passed to the callback function when the state changes

The handle function above contains resolve and reject, two functions that can be used to change the state of a Promise and pass in the Promise’s value

new Promise((resolve, reject) = > {
  setTimeout((a)= > {
    resolve('FULFILLED')},1000)})Copy the code

The “FULFILLED” that resolve passes in is the Promise value

Resolve and reject

  • resolve: Changes the state of the Promise object fromPendingintoThis is a big pity.
  • reject: Changes the state of the Promise object fromPendingintoRejected(failed)
  • resolverejectCan be passed a value of any type as an argumentPromiseObject successfully(Fulfilled)And failure(Rejected)The value of the

Now that we know the state and value of the Promise, let’s add the state properties and values to MyPromise

Start by defining three constants that mark the three states of the Promise object

// Define the three state constants for Promise
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
Copy the code

Add state and value to MyPromise, and add execution logic for state changes

class MyPromise {
  constructor (handle) {
    if(! isFunction(handle)) {throw new Error('MyPromise must accept a function as a parameter')}// Add state
    this._status = PENDING
    // Add state
    this._value = undefined
    / / handle
    try {
      handle(this._resolve.bind(this), this._reject.bind(this))}catch (err) {
      this._reject(err)
    }
  }
  // The function to execute when resovle is added
  _resolve (val) {
    if (this._status ! == PENDING)return
    this._status = FULFILLED
    this._value = val
  }
  // The function executed when adding reject
  _reject (err) { 
    if (this._status ! == PENDING)return
    this._status = REJECTED
    this._value = err
  }
}
Copy the code

This implements the Promise state and value change. Here’s the heart of promises: the then method

3. Promisethenmethods

The then method of the Promise object takes two arguments:

promise.then(onFulfilled, onRejected)
Copy the code

Parameters can be chosen

OnFulfilled and onRejected are both optional parameters.

  • ifonFulfilledonRejectedIs not a function, which must be ignored

onFulfilledfeatures

If ondepressing is a function:

  • whenpromiseMust be called when the status becomes successful, with the first argument beingpromiseThe value passed in for success status (resolveValue passed in at execution)
  • inpromiseIt cannot be called until the state changes
  • It cannot be invoked more than once

onRejectedfeatures

If onRejected is a function:

  • whenpromiseMust be called when the state becomes failed, and its first argument ispromiseThe value passed in by the failed state (rejectValue passed in at execution)
  • inpromiseIt cannot be called until the state changes
  • It cannot be invoked more than once

Multiple calls

The THEN method can be called multiple times by the same Promise object

  • whenpromiseSuccess status when allonFulfilledCallbacks must be made in the order in which they were registered
  • whenpromiseFailed state when allonRejectedCallbacks must be made in the order in which they were registered

return

The then method must return a new Promise object

promise2 = promise1.then(onFulfilled, onRejected);
Copy the code

So Promise supports chained invocation

promise1.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2);
Copy the code

There are implementation rules for promises, including “value passing” and “error catching” mechanisms:

This Promise will be fulfilled if onFulfilled or onRejected returns a value x: [[Resolve]](promise2, x)

  • ifxDon’t forPromise, then makexDirectly as the newly returnedPromiseObject value, that is, newonFulfilledoronRejectedArguments to a function.
  • ifxPromise, the latter callback will wait for thePromiseThe object (i.e.x) state changes before it is called, and newPromiseState andxIs in the same state.

The following example is to help you understand:

let promise1 = new Promise((resolve, reject) = > {
  setTimeout((a)= > {
    resolve()
  }, 1000)
})
promise2 = promise1.then(res= > {
  // Return a normal value
  return 'Return a normal value here.'
})
promise2.then(res= > {
  console.log(res) // Prints after 1 second: a normal value is returned
})
Copy the code
let promise1 = new Promise((resolve, reject) = > {
  setTimeout((a)= > {
    resolve()
  }, 1000)
})
promise2 = promise1.then(res= > {
  // Return a Promise object
  return new Promise((resolve, reject) = > {
    setTimeout((a)= > {
     resolve('Return a Promise here.')},2000)
  })
})
promise2.then(res= > {
  console.log(res) // Prints after 3 seconds: returns a Promise
})
Copy the code

This is a big pity if onFulfilled or onRejected throws an exception (e), then promise2 must become failure and return the defeatvalue (e), such as:

let promise1 = new Promise((resolve, reject) = > {
  setTimeout((a)= > {
    resolve('success')},1000)
})
promise2 = promise1.then(res= > {
  throw new Error('Here we throw an exception e'.)
})
promise2.then(res= > {
  console.log(res)
}, err => {
  console.log(err) // Prints after 1 second: here an exception e is thrown
})
Copy the code

This is a big pity, which is very depressing, and the state of promise1 must be Fulfilled. The value of promise1 success must be returned, which is very depressing, for example:

let promise1 = new Promise((resolve, reject) = > {
  setTimeout((a)= > {
    resolve('success')},1000)
})
promise2 = promise1.then('Ondepressing here was supposed to be a function, but now it is not.')
promise2.then(res= > {
  console.log(res) // After 1 second, print: SUCCESS
}, err => {
  console.log(err)
})
Copy the code

If onRejected is not a function and the status of promise1 is failed, promise2 must become failed and return the value of promise1, for example:

let promise1 = new Promise((resolve, reject) = > {
  setTimeout((a)= > {
    reject('fail')},1000)
})
promise2 = promise1.then(res= > res, 'onRejected was a function here, but it's not now')
promise2.then(res= > {
  console.log(res)
}, err => {
  console.log(err)  // Prints after 1 second: fail
})
Copy the code

According to the above rules, let’s improve MyPromise

Modify constructor: Add execution queue

Since the THEN method supports multiple calls, we can maintain two arrays, adding callback functions to the array for each then method registration and waiting to execute

constructor (handle) {
  if(! isFunction(handle)) {throw new Error('MyPromise must accept a function as a parameter')}// Add state
  this._status = PENDING
  // Add state
  this._value = undefined
  // Add the successful callback function queue
  this._fulfilledQueues = []
  // Add the failed callback function queue
  this._rejectedQueues = []
  / / handle
  try {
    handle(this._resolve.bind(this), this._reject.bind(this))}catch (err) {
    this._reject(err)
  }
}
Copy the code

Add then method

First, then returns a new Promise object, and the callback function needs to be added to the execution queue

// Add the then method
then (onFulfilled, onRejected) {
  const { _value, _status } = this
  switch (_status) {
    // When the state is pending, the then method callback is queued for execution
    case PENDING:
      this._fulfilledQueues.push(onFulfilled)
      this._rejectedQueues.push(onRejected)
      break
    // When the state has changed, the corresponding callback function is executed immediately
    case FULFILLED:
      onFulfilled(_value)
      break
    case REJECTED:
      onRejected(_value)
      break
  }
  // Return a new Promise object
  return new MyPromise((onFulfilledNext, onRejectedNext) = >{})}Copy the code

When does the returned Promise object change state? Which state is it going to be?

According to the rules of the THEN method above, we know that the state of the returned new Promise object depends on the execution of the current THEN method callback function and the return value, such as whether the argument of then is a function, whether the callback function is executed incorrectly, and whether the return value is a Promise object.

Let’s further refine the THEN method:

// Add the then method
then (onFulfilled, onRejected) {
  const { _value, _status } = this
  // Return a new Promise object
  return new MyPromise((onFulfilledNext, onRejectedNext) = > {
    // Encapsulates a function to execute on success
    let fulfilled = value= > {
      try {
        if(! isFunction(onFulfilled)) { onFulfilledNext(value) }else {
          let res =  onFulfilled(value);
          if (res instanceof MyPromise) {
            If the current callback returns a MyPromise object, you must wait for its state to change before executing the next callback
            res.then(onFulfilledNext, onRejectedNext)
          } else {
            Otherwise, the result is passed directly as an argument to the callback function of the next THEN, and the callback function of the next THEN is executed immediately
            onFulfilledNext(res)
          }
        }
      } catch (err) {
        // If the function fails, the state of the new Promise object is failed
        onRejectedNext(err)
      }
    }
    // Encapsulates a function to execute on failure
    let rejected = error= > {
      try {
        if(! isFunction(onRejected)) { onRejectedNext(error) }else {
            let res = onRejected(error);
            if (res instanceof MyPromise) {
              If the current callback returns a MyPromise object, you must wait for its state to change before executing the next callback
              res.then(onFulfilledNext, onRejectedNext)
            } else {
              Otherwise, the result is passed directly as an argument to the callback function of the next THEN, and the callback function of the next THEN is executed immediately
              onFulfilledNext(res)
            }
        }
      } catch (err) {
        // If the function fails, the state of the new Promise object is failed
        onRejectedNext(err)
      }
    }
    switch (_status) {
      // When the state is pending, the then method callback is queued for execution
      case PENDING:
        this._fulfilledQueues.push(fulfilled)
        this._rejectedQueues.push(rejected)
        break
      // When the state has changed, the corresponding callback function is executed immediately
      case FULFILLED:
        fulfilled(_value)
        break
      case REJECTED:
        rejected(_value)
        break}})}Copy the code

This part may not be easy to understand, so the reader needs to analyze it carefully with the rules of the THEN method mentioned above.

Then change _resolve and _reject: execute the functions in the queue in turn

When resolve or reject is executed, we extract the function in the successful or failed task queue and empty the queue, thus implementing multiple calls to then method, as follows:

// The function to execute when resovle is added
_resolve (val) {
  if (this._status ! == PENDING)return
  // Execute the functions in the success queue in turn and empty the queue
  const run = (a)= > {
    this._status = FULFILLED
    this._value = val
    let cb;
    while (cb = this._fulfilledQueues.shift()) {
      cb(val)
    }
  }
  // To support synchronous promises, asynchronous calls are used
  setTimeout((a)= > run(), 0)}// The function executed when adding reject
_reject (err) { 
  if (this._status ! == PENDING)return
  // Execute the functions in the failure queue in turn and empty the queue
  const run = (a)= > {
    this._status = REJECTED
    this._value = err
    let cb;
    while (cb = this._rejectedQueues.shift()) {
      cb(err)
    }
  }
  // To support synchronous promises, asynchronous calls are used
  setTimeout(run, 0)}Copy the code

There is a special case where the resolve method takes a Promise object as an argument, and the state of the Promise object determines the state of the current Promise object.

const p1 = new Promise(function (resolve, reject) {
  // ...
});

const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})
Copy the code

In the code above, p1 and p2 are both instances of Promise, but P2’s resolve method takes P1 as an argument, meaning that the result of an asynchronous operation is the return of another asynchronous operation.

Notice that the state of P1 is passed to p2, that is, the state of P1 determines the state of P2. If P1’s state is Pending, p2’s callback waits for p1’s state to change. If P1 has Fulfilled Fulfilled or Rejected, then P2’s callback function will be executed immediately.

Let’s modify _resolve to support such a feature

  // The function to execute when resovle is added
  _resolve (val) {
    const run = (a)= > {
      if (this._status ! == PENDING)return
      // Execute the functions in the success queue in turn and empty the queue
      const runFulfilled = (value) = > {
        let cb;
        while (cb = this._fulfilledQueues.shift()) {
          cb(value)
        }
      }
      // Execute the functions in the failure queue in turn and empty the queue
      const runRejected = (error) = > {
        let cb;
        while (cb = this._rejectedQueues.shift()) {
          cb(error)
        }
      }
      /* If the resolve argument is a Promise object, the current Promsie state must wait until the Promise object state changes, and the state depends on the Promsie object state */
      if (val instanceof MyPromise) {
        val.then(value= > {
          this._value = value
          this._status = FULFILLED
          runFulfilled(value)
        }, err => {
          this._value = err
          this._status = REJECTED
          runRejected(err)
        })
      } else {
        this._value = val
        this._status = FULFILLED
        runFulfilled(val)
      }
    }
    // To support synchronous promises, asynchronous calls are used
    setTimeout(run, 0)}Copy the code

Now that a Promise is basically fulfilled, let’s add some other methods

Catch method

This is equivalent to calling the THEN method, but passing in only the callback function with the Rejected state

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

Static resolve method

// Add static resolve method
static resolve (value) {
  // If the argument is MyPromise instance, return that instance directly
  if (value instanceof MyPromise) return value
  return new MyPromise(resolve= > resolve(value))
}
Copy the code

Static reject method

// Add static reject
static reject (value) {
  return new MyPromise((resolve ,reject) = > reject(value))
}
Copy the code

Static all method

// Add a static all method
static all (list) {
  return new MyPromise((resolve, reject) = > {
    /** * returns a set of values */
    let values = []
    let count = 0
    for (let [i, p] of list.entries()) {
      // If the array argument is not an instance of MyPromise, call myPromise.resolve first
      this.resolve(p).then(res= > {
        values[i] = res
        count++
        // This is a big pity. The MyPromise state will become a big pity
        if (count === list.length) resolve(values)
      }, err => {
        // MyPromise becomes rejected when there is a rejected state
        reject(err)
      })
    }
  })
}
Copy the code

Static RACE method

// Add static race methods
static race (list) {
  return new MyPromise((resolve, reject) = > {
    for (let p of list) {
      // The state of the new MyPromise changes as soon as one instance changes the state first
      this.resolve(p).then(res= > {
        resolve(res)
      }, err => {
        reject(err)
      })
    }
  })
}
Copy the code

The finally method

Is the finally method used to specify actions that will be performed regardless of the final state of the Promise object

finally (cb) {
  return this.then(
    value= > MyPromise.resolve(cb()).then((a)= > value),
    reason => MyPromise.resolve(cb()).then((a)= > { throw reason })
  );
};
Copy the code

This is a complete Promsie implementation, and you have an understanding of how promises work, which will make it clear when to use promises.

The complete code is as follows

  // check whether the variable is function
  const isFunction = variable= > typeof variable === 'function'
  // Define the three state constants for Promise
  const PENDING = 'PENDING'
  const FULFILLED = 'FULFILLED'
  const REJECTED = 'REJECTED'

  class MyPromise {
    constructor (handle) {
      if(! isFunction(handle)) {throw new Error('MyPromise must accept a function as a parameter')}// Add state
      this._status = PENDING
      // Add state
      this._value = undefined
      // Add the successful callback function queue
      this._fulfilledQueues = []
      // Add the failed callback function queue
      this._rejectedQueues = []
      / / handle
      try {
        handle(this._resolve.bind(this), this._reject.bind(this))}catch (err) {
        this._reject(err)
      }
    }
    // The function to execute when resovle is added
    _resolve (val) {
      const run = (a)= > {
        if (this._status ! == PENDING)return
        // Execute the functions in the success queue in turn and empty the queue
        const runFulfilled = (value) = > {
          let cb;
          while (cb = this._fulfilledQueues.shift()) {
            cb(value)
          }
        }
        // Execute the functions in the failure queue in turn and empty the queue
        const runRejected = (error) = > {
          let cb;
          while (cb = this._rejectedQueues.shift()) {
            cb(error)
          }
        }
        /* If the resolve argument is a Promise object, the current Promsie state must wait until the Promise object state changes, and the state depends on the Promsie object state */
        if (val instanceof MyPromise) {
          val.then(value= > {
            this._value = value
            this._status = FULFILLED
            runFulfilled(value)
          }, err => {
            this._value = err
            this._status = REJECTED
            runRejected(err)
          })
        } else {
          this._value = val
          this._status = FULFILLED
          runFulfilled(val)
        }
      }
      // To support synchronous promises, asynchronous calls are used
      setTimeout(run, 0)}// The function executed when adding reject
    _reject (err) { 
      if (this._status ! == PENDING)return
      // Execute the functions in the failure queue in turn and empty the queue
      const run = (a)= > {
        this._status = REJECTED
        this._value = err
        let cb;
        while (cb = this._rejectedQueues.shift()) {
          cb(err)
        }
      }
      // To support synchronous promises, asynchronous calls are used
      setTimeout(run, 0)}// Add the then method
    then (onFulfilled, onRejected) {
      const { _value, _status } = this
      // Return a new Promise object
      return new MyPromise((onFulfilledNext, onRejectedNext) = > {
        // Encapsulates a function to execute on success
        let fulfilled = value= > {
          try {
            if(! isFunction(onFulfilled)) { onFulfilledNext(value) }else {
              let res =  onFulfilled(value);
              if (res instanceof MyPromise) {
                If the current callback returns a MyPromise object, you must wait for its state to change before executing the next callback
                res.then(onFulfilledNext, onRejectedNext)
              } else {
                Otherwise, the result is passed directly as an argument to the callback function of the next THEN, and the callback function of the next THEN is executed immediately
                onFulfilledNext(res)
              }
            }
          } catch (err) {
            // If the function fails, the state of the new Promise object is failed
            onRejectedNext(err)
          }
        }
        // Encapsulates a function to execute on failure
        let rejected = error= > {
          try {
            if(! isFunction(onRejected)) { onRejectedNext(error) }else {
                let res = onRejected(error);
                if (res instanceof MyPromise) {
                  If the current callback returns a MyPromise object, you must wait for its state to change before executing the next callback
                  res.then(onFulfilledNext, onRejectedNext)
                } else {
                  Otherwise, the result is passed directly as an argument to the callback function of the next THEN, and the callback function of the next THEN is executed immediately
                  onFulfilledNext(res)
                }
            }
          } catch (err) {
            // If the function fails, the state of the new Promise object is failed
            onRejectedNext(err)
          }
        }
        switch (_status) {
          // When the state is pending, the then method callback is queued for execution
          case PENDING:
            this._fulfilledQueues.push(fulfilled)
            this._rejectedQueues.push(rejected)
            break
          // When the state has changed, the corresponding callback function is executed immediately
          case FULFILLED:
            fulfilled(_value)
            break
          case REJECTED:
            rejected(_value)
            break}})}// Add the catch method
    catch (onRejected) {
      return this.then(undefined, onRejected)
    }
    // Add static resolve method
    static resolve (value) {
      // If the argument is MyPromise instance, return that instance directly
      if (value instanceof MyPromise) return value
      return new MyPromise(resolve= > resolve(value))
    }
    // Add static reject
    static reject (value) {
      return new MyPromise((resolve ,reject) = > reject(value))
    }
    // Add a static all method
    static all (list) {
      return new MyPromise((resolve, reject) = > {
        /** * returns a set of values */
        let values = []
        let count = 0
        for (let [i, p] of list.entries()) {
          // If the array argument is not an instance of MyPromise, call myPromise.resolve first
          this.resolve(p).then(res= > {
            values[i] = res
            count++
            // This is a big pity. The MyPromise state will become a big pity
            if (count === list.length) resolve(values)
          }, err => {
            // MyPromise becomes rejected when there is a rejected state
            reject(err)
          })
        }
      })
    }
    // Add static race methods
    static race (list) {
      return new MyPromise((resolve, reject) = > {
        for (let p of list) {
          // The state of the new MyPromise changes as soon as one instance changes the state first
          this.resolve(p).then(res= > {
            resolve(res)
          }, err => {
            reject(err)
          })
        }
      })
    }
    finally (cb) {
      return this.then(
        value= > MyPromise.resolve(cb()).then((a)= > value),
        reason => MyPromise.resolve(cb()).then((a)= > { throwreason }) ); }}Copy the code

If it’s okay, just like it and bookmark it before you leave.