Promise implementation is not as simple or as difficult as it might seem, and it takes less than 200 lines of code to implement an alternative to the original Promise.

Promises have been an integral API on the front end and are now ubiquitous. Are you sure you know Promise well enough? If not, you should know how Promise works. If you think you know it, have you ever fulfilled your Promise?

In any case, understanding how promises are implemented can help improve our front-end skills.

The following code is implemented using ES6 syntax, is not COMPATIBLE with ES5, and runs fine on the latest Google Browser.

If you want to see the results first, check out the full version at the end of this article, or check out Github, which includes unit tests.

The origin of the Promise

As a front end, the general first touch on the Promise concept was deferred Objects, released in version 1.5 of jquery. But it wasn’t jquery that first introduced the concept of Promise on the front end, it was Dojo, and that’s why promises are called Promises. Promises/A writer KrisZyp wrote A post on Google’s CommonJS discussion group in 2009 discussing the Promise API. He claimed that he wanted to name the API future, but the Deferred mechanism that Dojo already implemented used the term Promise, so he stuck with the Promise name.

Update log

2019-10-12

  • Removed this._callbcaQueue and replaced with this._nextCallback

    Since the chain returns a new Promise, there is really only one callback and no array handling is required

Part of the term

  • executor

    new Promise( function(resolve, reject) {... }/* executor */  );
    Copy the code

    Executor is a function that takes resolve and reject.

  • onFulfilled

    p.then(onFulfilled, onRejected);
    Copy the code

    This parameter is called as a then callback function when a Promise becomes fulfillment.

  • OnRejected This parameter is called as a callback when a Promise turns into a rejection.

    p.then(onFulfilled, onRejected);
    p.catch(onRejected);
    Copy the code

Promise implementation Principle

Better to look at Promises/A+ specifications first, here is A personal summary of the fundamentals of code implementation.

Three states of Promise

  • pending
  • fulfilled
  • rejected

The Promise object’s pending may become a pity and Rejected, but it cannot be reversed.

The then and catch callback methods can only be executed in a non-pending state.

Promise lifecycle

In order to better understand, I summarized the life cycle of Promise. The life cycle is divided into two situations and the life cycle is irreversible.

pending -> fulfilld

pending -> rejected

** Executor, then, Catch, and finally all have their own new life cycle, which is their own separate Promise environment. The result of the next Promise returned by the ** chain is derived from the result of the previous Promise.

Principle of the chain

How do you keep chain calls to.then,.catch, and.finally?

Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises Meanwhile, the initial state of each chained Promise is guaranteed to be pending, and each THEN, catch and finally has its own Promise life cycle.

Promise.prototype.then = function(onFulfilled,onReject) {
  return new Promise((a)= > {
    // Handle the subsequent logic here})}Copy the code

However, we need to consider the case that the link is broken midway. If the link continues after the break, the state of the Promise may be non-pending.

This point is not so easy to understand when first exposed to.

The default is to start with new Promise(…) .then(…) In chain calls, then, catch, and other callback functions are all in pending state, and the callback functions are added to the asynchronous queue waiting for execution. In case of chain disconnection, the chain call may be repeated after several seconds, and the pending state may change to the fulfilled or Rejected state, which needs to be executed immediately instead of waiting for the pending state to change.

Examples of continuous chains are as follows:

new Promise(...). .then(...)Copy the code

Examples of broken links are as follows:

const a = new Promise(...).// This may be a pity or rejected state after a few seconds
a.then(...)
Copy the code

So you have to think about both cases.

Asynchronous lined up

This requires an understanding of macro tasks and microtasks, but not all browser JavaScript apis provide such methods.

Instead of using setTimeout, the main purpose is to wait for the pending state to change to another state (executor execution) before performing subsequent chained callbacks.

Resolve or reject can also be implemented asynchronously. But native Promise all then callbacks are executed in an asynchronous queue, even if it’s synchronous resolve or reject.

new Promise(resolve= >{
  resolve('This is sync resolve')})new Promise(resolve= >{
  setTimeout((a)= >{
  	resolve('This is asynchronous resolve')})})Copy the code

Note that in the step-by-step examples below, asynchronous processing is excluded from the previous code implementation and resolved or reject scenarios are added.

The first step is to define the structure

To make it different from the original Promise, we prefix it to NPromise.

Define state

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected
Copy the code

Define the Promise instance method

class NPromise {
  constructor(executor) {}
  then(onFulfilled, onRejected) {}
  catch(onRejected) {}
  finally(onFinally) {}
}
Copy the code

Define extension methods

NPromise.resolve = function(value){}
NPromise.reject = function(resaon){}
NPromise.all = function(values){}
NPromise.race = function(values){}
Copy the code

A simple Promise

The first simple Promsie does not take into account cases such as asynchronous resolve, which is only used like, and the callback to THEN is not performed asynchronously.

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

/** * @param {Function} executor * Executor is a Function that takes resolve and reject * the Promise constructor calls the executor Function immediately when it executes */
class NPromise {
  constructor(executor) {
    // There are some attribute variables that can not be defined, but define them in advance to improve readability

    // Initialize the status to pending
    this._status = PENDING
    // The ondepressing parameter to pass to then
    this._nextValue = undefined
    // Error cause
    this._error = undefined
    executor(this._onFulfilled, this._onRejected)
  }

  @param {Any} value The value passed successfully */
  _onFulfilled = (value) = > {
    if (this._status === PENDING) {
      this._status = FULFILLED
      this._nextValue = value
      this._error = undefined}}/**
   * 操作失败
   * @param {Any} reason 操作失败传递的值
   */
  _onRejected = (reason) = > {
    if (this._status === PENDING) {
      this._status = REJECTED
      this._error = reason
      this._nextValue = undefined
    }
  }

  then(onFulfilled, onRejected) {
    return new NPromise((resolve, reject) = > {
      if (this._status === FULFILLED) {
        if (onFulfilled) {
          const _value = onFulfilled
            ? onFulfilled(this._nextValue)
            : this._nextValue
          // If onFulfilled has a definition, run onFulfilled and return the result
          // Otherwise skip this, the next promises will all return the depressing state
          resolve(_value)
        }
      }

      if (this._status === REJECTED) {
        if (onRejected) {
          resolve(onRejected(this._error))
        } else {
          // The next Promise returns the Rejected state without a direct skip
          reject(this._error)
        }
      }
    })
  }

  catch(onRejected) {
    // This is a big pity
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    // This is implemented later}}Copy the code

Test example (setTimeout only to provide a separate execution environment)

setTimeout((a)= > {
  new NPromise((resolve) = > {
    console.log('resolved:')
    resolve(1)
  })
    .then((value) = > {
      console.log(value)
      return 2
    })
    .then((value) = > {
      console.log(value)
    })
})

setTimeout((a)= > {
  new NPromise((_, reject) = > {
    console.log('rejected:')
    reject('err')
  })
    .then((value) = > {
      // It will not run here
      console.log(value)
      return 2
    })
    .catch((err) = > {
      console.log(err)
    })
})
/ / output
// resolved:
/ / 1
/ / 2
// rejected:
// err
Copy the code

Consider catching errors

The resolve callback is not performed asynchronously. The resolve callback is not performed asynchronously.

Change points relative to previous step:

  • Executor execution requires a try catch

    Then, catch, and finally are all executed by executor, so you only need to try catch executor.

  • If an error is not caught using promiseinstance. catch, the error message is printed

    It’s not a throw, it’s a console.error, which is also native, so you can’t catch a Promise error by trying it.

  • If the callback function for then is not a function type, skip it

    This is outside the scope of error capture, but the change point is mentioned here in passing.

Code implementation

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

function isFunction(fn) {
  return typeof fn === 'function'
}

/** * @param {Function} executor * Executor is a Function that takes resolve and reject * the Promise constructor calls the executor Function immediately when it executes */
class NPromise {
  constructor(executor) {
    // There are some attribute variables that can not be defined, but define them in advance to improve readability

    try {
      // Initialize the status to pending
      this._status = PENDING
      // The ondepressing parameter to pass to then
      this._nextValue = undefined
      // Error cause
      this._error = undefined
      executor(this._onFulfilled, this._onRejected)
    } catch (err) {
      this._onRejected(err)
    }
  }

  /** * if there is no.catch error, the error */ is thrown at the end
  _throwErrorIfNotCatch() {
    setTimeout((a)= > {
      // setTimeout is required to wait for execution to complete, and finally check if this._error is still defined
      if (this._error ! = =undefined) {
        // Do not catch an error
        console.error('Uncaught (in promise)'.this._error)
      }
    })
  }

  @param {Any} value The value passed successfully */
  _onFulfilled = (value) = > {
    if (this._status === PENDING) {
      this._status = FULFILLED
      this._nextValue = value
      this._error = undefined
      this._throwErrorIfNotCatch()
    }
  }

  /**
   * 操作失败
   * @param {Any} reason 操作失败传递的值
   */
  _onRejected = (reason) = > {
    if (this._status === PENDING) {
      this._status = REJECTED
      this._error = reason
      this._nextValue = undefined
      this._throwErrorIfNotCatch()
    }
  }

  then(onFulfilled, onRejected) {
    return new NPromise((resolve, reject) = > {
      const handle = (reason) = > {
        function handleResolve(value) {
          const _value = isFunction(onFulfilled) ? onFulfilled(value) : value
          resolve(_value)
        }

        function handleReject(err) {
          if (isFunction(onRejected)) {
            resolve(onRejected(err))
          } else {
            reject(err)
          }
        }

        if (this._status === FULFILLED) {
          return handleResolve(this._nextValue)
        }

        if (this._status === REJECTED) {
          return handleReject(reason)
        }
      }
      handle(this._error)
      // The error has been passed to the next NPromise and needs to be reset otherwise multiple identical errors will be printed
      // With this._throwErrorIfnotCatch,
      // Ensure that an error is thrown at the end of execution, if there is no catch
      this._error = undefined})}catch(onRejected) {
    // This is a big pity
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    // This is implemented later}}Copy the code

Test example (setTimeout only to provide a separate execution environment)

setTimeout((a)= > {
  new NPromise((resolve) = > {
    console.log('executor error:')
    const a = 2
    a = 3
    resolve(1)
  }).catch((value) = > {
    console.log(value)
    return 2
  })
})

setTimeout((a)= > {
  new NPromise((resolve) = > {
    resolve()
  })
    .then((a)= > {
      const b = 3
      b = 4
      return 2
    })
    .catch((err) = > {
      console.log('then callback error: ')
      console.log(err)
    })
})

setTimeout((a)= > {
  new NPromise((resolve) = > {
    console.log('Error message printed directly, red:')
    resolve()
  }).then((a)= > {
    throw Error('test')
    return 2})})// Executor reported an error:
// TypeError: Assignment to constant variable.
// at 
      
       :97:7
      
// at new NPromise (
      
       :21:7)
      
// at 
      
       :94:3
      
TypeError: Assignment to constant variable
// at 
      
       :111:9
      
// at 
      
       :59:17
      
// at new Promise (
      
       )
      
// at NPromise.then (
      
       :54:12)
      
// at 
      
       :109:6
      
// Error message printed directly, red:
// Uncaught (in promise) Error: test
// at 
      
       :148:11
      
// at handleResolve (
      
       :76:52)
      
// at handle (
      
       :89:18)
      
// at 
      
       :96:7
      
// at new NPromise (
      
       :25:7)
      
// at NPromise.then (
      
       :73:12)
      
// at 
      
       :147:6
      
Copy the code

The value considered resolved is of type Promise

The resolve callback is not performed asynchronously. The resolve callback is not performed asynchronously.

Change points relative to previous step:

  • New isPromise method
  • The then method handles the case where this._nextValue is a Promise

Code implementation

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

function isFunction(fn) {
  return typeof fn === 'function'
}

function isPromise(value) {
  return (
    value &&
    isFunction(value.then) &&
    isFunction(value.catch) &&
    isFunction(value.finally)
    // We can also use a line of code to replace the above judgment, but we tried it natively, not in the following way
    // value instanceof NPromise)}/** * @param {Function} executor * Executor is a Function that takes resolve and reject * the Promise constructor calls the executor Function immediately when it executes */
class NPromise {
  constructor(executor) {
    // There are some attribute variables that can not be defined, but define them in advance to improve readability

    try {
      // Initialize the status to pending
      this._status = PENDING
      // The ondepressing parameter to pass to then
      this._nextValue = undefined
      // Error cause
      this._error = undefined
      executor(this._onFulfilled, this._onRejected)
    } catch (err) {
      this._onRejected(err)
    }
  }

  /** * if there is no.catch error, the error */ is thrown at the end
  _throwErrorIfNotCatch() {
    setTimeout((a)= > {
      // setTimeout is required to wait for execution to complete, and finally check if this._error is still defined
      if (this._error ! = =undefined) {
        // Do not catch an error
        console.error('Uncaught (in promise)'.this._error)
      }
    })
  }

  @param {Any} value The value passed successfully */
  _onFulfilled = (value) = > {
    if (this._status === PENDING) {
      this._status = FULFILLED
      this._nextValue = value
      this._error = undefined
      this._throwErrorIfNotCatch()
    }
  }

  /**
   * 操作失败
   * @param {Any} reason 操作失败传递的值
   */
  _onRejected = (reason) = > {
    if (this._status === PENDING) {
      this._status = REJECTED
      this._error = reason
      this._nextValue = undefined
      this._throwErrorIfNotCatch()
    }
  }

  then(onFulfilled, onRejected) {
    return new NPromise((resolve, reject) = > {
      const handle = (reason) = > {
        function handleResolve(value) {
          const _value = isFunction(onFulfilled) ? onFulfilled(value) : value
          resolve(_value)
        }

        function handleReject(err) {
          if (isFunction(onRejected)) {
            resolve(onRejected(err))
          } else {
            reject(err)
          }
        }

        if (this._status === FULFILLED) {
          if (isPromise(this._nextValue)) {
            return this._nextValue.then(handleResolve, handleReject)
          } else {
            return handleResolve(this._nextValue)
          }
        }

        if (this._status === REJECTED) {
          return handleReject(reason)
        }
      }
      handle(this._error)
      // The error has been passed to the next NPromise and needs to be reset otherwise multiple identical errors will be printed
      // With this._throwErrorIfnotCatch,
      // Ensure that an error is thrown at the end of execution, if there is no catch
      this._error = undefined})}catch(onRejected) {
    // This is a big pity
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    // This is implemented later}}Copy the code

The test code


setTimeout((a)= > {
  new NPromise((resolve) = > {
    resolve(
      new NPromise((_resolve) = > {
        _resolve(1)
      })
    )
  }).then((value) = > {
    console.log(value)
  })
})

setTimeout((a)= > {
  new NPromise((resolve) = > {
    resolve(
      new NPromise((_, _reject) = > {
        _reject('err')
      })
    )
  }).catch((err) = > {
    console.log(err)
  })
})
Copy the code

Consider the asynchronous case

Async Resolve or REJECT, which are more complex, waits for the pending state to change before executing.

Native JavaScript comes with asynchronous queuing, and we can take advantage of that by using setTimeout instead, So this Promise has the same level of priority as setTimeout (natively, the Promise takes precedence over setTimeout).

Change points relative to previous step:

  • This. _ondepressing is fulfilled with asynchronous processing, that is, the so-called asynchronous procession will be added

  • Enclosing _onRejected with asynchronous processing, namely to join the so-called asynchronous lined up

  • This new _nextCallback

    This._nextCallback will only have a value if the Promise state is pending (the callback will execute immediately if the Promise state is not pending). Wait for the pending state to change before executing.

    This._nextcallback will only be executed once in the current Promise life cycle.

  • The then method needs to discriminate according to the Promise state

    If the state is not pending, execute the callback function immediately (skip if there is no callback function).

    If the state is pending, join an asynchronous queue (passing a callback through this._nextcallback) and wait until the Promise state is non-pending.

  • The callback to the then method adds try catch error handling

  • Finally methods are implemented

Code implementation

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

function isFunction(fn) {
  return typeof fn === 'function'
}

function isPromise(value) {
  return (
    value &&
    isFunction(value.then) &&
    isFunction(value.catch) &&
    isFunction(value.finally)
    // We can also use a line of code to replace the above judgment, but we tried it natively, not in the following way
    // value instanceof NPromise)}/** * @param {Function} executor * Executor is a Function that takes resolve and reject * the Promise constructor calls the executor Function immediately when it executes */
class NPromise {
  constructor(executor) {
    if(! isFunction(executor)) {throw new TypeError('Expected the executor to be a function.')}try {
      // Initialize the status to PENDING
      this._status = PENDING
      // The value of the fullfilled then and catch return are temporary values currently executed
      this._nextValue = undefined
      // Error message currently captured
      this._error = undefined
      // Then, catch, and finally asynchronous callbacks are queued up and executed in sequence
      // Change: Since then and catch return new promises, no queue execution is required, just nextCallback
      // this._callbacQueue = []
      // Asynchronous resolve or reject processes wait for pending state transitions before executing the next callback
      this._nextCallback = undefined
      executor(this._onFulfilled, this._onRejected)
    } catch (err) {
      this._onRejected(err)
    }
  }

  /** * if there is no.catch error, the error */ is thrown at the end
  _throwErrorIfNotCatch() {
    setTimeout((a)= > {
      // setTimeout is required to wait for execution to complete, and finally check if this._error is still defined
      if (this._error ! = =undefined) {
        // Do not catch an error
        console.error('Uncaught (in promise)'.this._error)
      }
    })
  }

  @param {Any} value The value passed successfully */
  _onFulfilled = value= > {
    setTimeout((a)= > {
      if (this._status === PENDING) {
        this._status = FULFILLED
        this._nextValue = value
        this._error = undefined
        this._nextCallback && this._nextCallback()
        this._throwErrorIfNotCatch()
      }
    })
  }

  /**
   * 操作失败
   * @param {Any} reason 操作失败传递的值
   */
  _onRejected = reason= > {
    setTimeout((a)= > {
      if (this._status === PENDING) {
        this._status = REJECTED
        this._error = reason
        this._nextValue = undefined
        this._nextCallback && this._nextCallback()
        this._throwErrorIfNotCatch()
      }
    })
  }

  then(onFulfilled, onRejected) {
    return new NPromise((resolve, reject) = > {
      const handle = reason= > {
        try {
          function handleResolve(value) {
            const _value = isFunction(onFulfilled) ? onFulfilled(value) : value
            resolve(_value)
          }

          function handleReject(err) {
            if (isFunction(onRejected)) {
              resolve(onRejected(err))
            } else {
              reject(err)
            }
          }

          if (this._status === FULFILLED) {
            if (isPromise(this._nextValue)) {
              return this._nextValue.then(handleResolve, handleReject)
            } else {
              return handleResolve(this._nextValue)
            }
          }

          if (this._status === REJECTED) {
            return handleReject(reason)
          }
        } catch (err) {
          reject(err)
        }
      }

      const nextCallback = (a)= > {
        handle(this._error)
        // The error has been passed to the next NPromise and needs to be reset otherwise multiple identical errors will be thrown
        // With this._throwErrorIfnotCatch,
        // Ensure that an error is thrown at the end of execution, if there is no catch
        this._error = undefined
      }

      if (this._status === PENDING) {
        // By default, the state of then return function Promise is pending.
        / / such as new NPromise (...). .then(...) It's not broken
        // The link is broken
        // var a = NPromise(...) ; a.then(...)
        // It can also be a pending state
        this._nextCallback = nextCallback
      } else {
        // The state of the then callback is not pending
        Var a = NPromise(...) ;
        // after a few seconds a.chen (...) If you continue the chain call, you may be in a non-pending state
        nextCallback() // Needs to be executed immediately}})}catch(onRejected) {
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    return this.then(
      (a)= > {
        onFinally()
        return this._nextValue
      },
      () => {
        onFinally()
        // The error needs to be thrown for the next Promise to be caught
        throw this._error
      }
    )
  }
}
Copy the code

The test code

new NPromise((resolve) = > {
  setTimeout((a)= > {
    resolve(1)},1000)
})
  .then((value) = > {
    console.log(value)
    return new NPromise((resolve) = > {
      setTimeout((a)= > {
        resolve(2)},1000)
    })
  })
  .then((value) = > {
    console.log(value)
  })
Copy the code

Expanding method

The relatively difficult extension method should be Promise. All, the rest are quite simple.

NPromise.resolve = function(value) {
  return new NPromise(resolve= > {
    resolve(value)
  })
}

NPromise.reject = function(reason) {
  return new NPromise((_, reject) = > {
    reject(reason)
  })
}

NPromise.all = function(values) {
  return new NPromise((resolve, reject) = > {
    let ret = {}
    let isError = false
    values.forEach((p, index) = > {
      if (isError) {
        return
      }
      NPromise.resolve(p)
        .then(value= > {
          ret[index] = value
          const result = Object.values(ret)
          if (values.length === result.length) {
            resolve(result)
          }
        })
        .catch(err= > {
          isError = true
          reject(err)
        })
    })
  })
}

NPromise.race = function(values) {
  return new NPromise(function(resolve, reject) {
    values.forEach(function(value) {
      NPromise.resolve(value).then(resolve, reject)
    })
  })
}
Copy the code

The final version

You can also look at Github, which has unit tests.

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

function isFunction(fn) {
  return typeof fn === 'function'
}

function isPromise(value) {
  return (
    value &&
    isFunction(value.then) &&
    isFunction(value.catch) &&
    isFunction(value.finally)
    // We can also use a line of code to replace the above judgment, but we tried it natively, not in the following way
    // value instanceof NPromise)}/** * @param {Function} executor * Executor is a Function that takes resolve and reject * the Promise constructor calls the executor Function immediately when it executes */
class NPromise {
  constructor(executor) {
    if(! isFunction(executor)) {throw new TypeError('Expected the executor to be a function.')}try {
      // Initialize the status to PENDING
      this._status = PENDING
      // The value of the fullfilled then and catch return are temporary values currently executed
      this._nextValue = undefined
      // Error message currently captured
      this._error = undefined
      // Then, catch, and finally asynchronous callbacks are queued up and executed in sequence
      // Change: Since then and catch return new promises, no queue execution is required, just nextCallback
      // this._callbacQueue = []
      // Asynchronous resolve or reject processes wait for pending state transitions before executing the next callback
      this._nextCallback = undefined
      executor(this._onFulfilled, this._onRejected)
    } catch (err) {
      this._onRejected(err)
    }
  }

  /** * if there is no.catch error, the error */ is thrown at the end
  _throwErrorIfNotCatch() {
    setTimeout((a)= > {
      // setTimeout is required to wait for execution to complete, and finally check if this._error is still defined
      if (this._error ! = =undefined) {
        // Do not catch an error
        console.error('Uncaught (in promise)'.this._error)
      }
    })
  }

  @param {Any} value The value passed successfully */
  _onFulfilled = value= > {
    setTimeout((a)= > {
      if (this._status === PENDING) {
        this._status = FULFILLED
        this._nextValue = value
        this._error = undefined
        this._nextCallback && this._nextCallback()
        this._throwErrorIfNotCatch()
      }
    })
  }

  /**
   * 操作失败
   * @param {Any} reason 操作失败传递的值
   */
  _onRejected = reason= > {
    setTimeout((a)= > {
      if (this._status === PENDING) {
        this._status = REJECTED
        this._error = reason
        this._nextValue = undefined
        this._nextCallback && this._nextCallback()
        this._throwErrorIfNotCatch()
      }
    })
  }

  then(onFulfilled, onRejected) {
    return new NPromise((resolve, reject) = > {
      const handle = reason= > {
        try {
          function handleResolve(value) {
            const _value = isFunction(onFulfilled) ? onFulfilled(value) : value
            resolve(_value)
          }

          function handleReject(err) {
            if (isFunction(onRejected)) {
              resolve(onRejected(err))
            } else {
              reject(err)
            }
          }

          if (this._status === FULFILLED) {
            if (isPromise(this._nextValue)) {
              return this._nextValue.then(handleResolve, handleReject)
            } else {
              return handleResolve(this._nextValue)
            }
          }

          if (this._status === REJECTED) {
            return handleReject(reason)
          }
        } catch (err) {
          reject(err)
        }
      }

      const nextCallback = (a)= > {
        handle(this._error)
        // The error has been passed to the next NPromise and needs to be reset otherwise multiple identical errors will be thrown
        // With this._throwErrorIfnotCatch,
        // Ensure that an error is thrown at the end of execution, if there is no catch
        this._error = undefined
      }

      if (this._status === PENDING) {
        // By default, the state of then return function Promise is pending.
        / / such as new NPromise (...). .then(...) It's not broken
        // The link is broken
        // var a = NPromise(...) ; a.then(...)
        // It can also be a pending state
        this._nextCallback = nextCallback
      } else {
        // The state of the then callback is not pending
        Var a = NPromise(...) ;
        // after a few seconds a.chen (...) If you continue the chain call, you may be in a non-pending state
        nextCallback() // Needs to be executed immediately}})}catch(onRejected) {
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    return this.then(
      (a)= > {
        onFinally()
        return this._nextValue
      },
      () => {
        onFinally()
        // The error needs to be thrown for the next Promise to be caught
        throw this._error
      }
    )
  }
}

NPromise.resolve = function(value) {
  return new NPromise(resolve= > {
    resolve(value)
  })
}

NPromise.reject = function(reason) {
  return new NPromise((_, reject) = > {
    reject(reason)
  })
}

NPromise.all = function(values) {
  return new NPromise((resolve, reject) = > {
    let ret = {}
    let isError = false
    values.forEach((p, index) = > {
      if (isError) {
        return
      }
      NPromise.resolve(p)
        .then(value= > {
          ret[index] = value
          const result = Object.values(ret)
          if (values.length === result.length) {
            resolve(result)
          }
        })
        .catch(err= > {
          isError = true
          reject(err)
        })
    })
  })
}

NPromise.race = function(values) {
  return new NPromise(function(resolve, reject) {
    values.forEach(function(value) {
      NPromise.resolve(value).then(resolve, reject)
    })
  })
}
Copy the code

conclusion

The key to implementing promises is to switch states and then chain them (return the new Promise, core point). The main logic is the processing of the THEN method (core). If you understand the logic of the THEN method, you will understand most of it.

After some testing, except for the following two points:

  • Use setTimeout macro task queuing instead of microtasks
  • The extension methods promise. all and promise. race only consider arrays, not iterators.

NPromise alone is almost 100% identical in usage and effect to native.

If you don’t believe me, check out the unit tests on Github while you try the following code:

Notice NPromise and Promise.

new NPromise((resolve) = > {
  resolve(Promise.resolve(2))
}).then((value) = > {
  console.log(value)
})
Copy the code

or

new Promise((resolve) = > {
  resolve(NPromise.resolve(2))
}).then((value) = > {
  console.log(value)
})
Copy the code

All of the above results are returned to normal.

Refer to the article

  • Promises/A+

  • Let’s talk about promise’s past life