A, specification

Let’s start with the Promise/A+ specification requirements:

Promises represent the end result of asynchronous operations. The primary way to interact with a promise is through the then method, which registers callbacks to receive the final value of a promise or the reason why the promise didn’t complete.

  1. State of promise

    Pormise must be one of the three states: Pending, depressing, and Rejected.

    1. When a promise is in pending state:
      1. You can switch to the fulfilled or Rejected state.
    2. When the promise is fulfilled:
      1. Cannot transition to another state.
      2. There must be a value and it cannot be changed.
    3. When the promise is in the Rejected state:
      1. Cannot transition to another state.
      2. There must be reason and it cannot be changed.
  2. Then method

    A promise must provide a then method that accesses the current or final value or Reason.

    The then method of Pormise accepts two parameters promise. Then (onFulfilled, onRejected)

    1. OnFulfilled and onRejected are both optional parameters.

      1. ifonFulfilledIf not a function, ignore it.
      2. ifonRejectedIf not a function, ignore it.
    2. If ondepressing is a function:

      1. It must be inpromisefulfilledLater, in order topromisevalueCalled as the first argument.
      2. It can’t be inpromisefulfilledCall before.
      3. It cannot be called more than once.
    3. If onRejected is a function:

      1. It must be inpromiserejectedLater, in order topromisereasonCalled as the first argument.
      2. It can’t be inpromiserejectedCall before.
      3. It cannot be called more than once.
    4. OnFulfilled or onRejected cannot be called until the execution Context stack contains only the engine code.

    5. Ondepressing or onRejected must be called as a function.

    6. The then method can be called multiple times by the same promise.

      1. whenpromiseIn afulfilledStates, all of their ownonFulfilledThe callback function must be followedthenThe order of registration is called.
      2. whenpromiseIn arejectedStates, all of their ownonRejectedThe callback function must be followedthenThe order of registration is called.
    7. The then method must return a promise

      promise2 = promise1.then(onFulfilled, onRejected);

      1. ifonFulfilledoronRejectedReturn a valuex, the implementationPromise Resolution Procedure [[Resolve]](promise2, x).
      2. ifonFulfilledoronRejectedAn exception is thrownepromise2Have to beeAs reason, go to the Rejected state.
      3. ifonFulfilledIt’s not a function, andpromise1Is in a depressing state, thenpromise2Must be withpromise1The same value will be fulfilled.
      4. ifonRejectedIt’s not a function, andpromise1If the rejected state is in the rejected statepromise2Must be withpromise1There is no reason but rejected.
  3. Promise Resolution Procedure

    The Promise Resolution Procedure is an abstract operation. It takes a promise and a value as input, called: [[Resolve]](promise, x). If x is a Thenable, it will try to make the Promise the same state as X, provided x is a similar Promise object. Otherwise, it will make the promise move to a fulfilled state with x as value.

    This thenables treatment allows different Promises to interoperate as long as they expose A THEN method that complies with Promises/A+. It also allows Promises/A+ implementations to “assimilate” inconsistent implementations using sound THEN methods.

    [[Resolve]](promise, x) Do the following:

    1. If the promise and X reference the same object, a TypeError is used as reason to make the promise rejeted.

    2. If x is also a Promise, make the promise accept its state

      1. ifxIs in a pending state,promiseMust remain pending untilxThis will become a pity or rejected state.promiseSynchronously change.
      2. If or whenxThis is a big pity. I will gradually forget the song with the same valuepromiseThis will become a depressing state.
      3. If or whenxIn the rejected state, use the same reasonpromiseIt’s also the Rejected state.
    3. If x is an object or a function.

      1. Let then equal x. teng.

      2. If x. Chen is read and raises an exception e, use eas reason to change the Promise state to Rejected.

      3. If then is a function, call it with x as this, passing in the first argument resolvePromise and the second argument rejectPromise.

        1. If resolvePromise is called in y, execute [[Resolve]](promise, y)

        2. If rejectedPromise is called by r, use r as reason to change the promise state to Rejected

        3. If both resolvePromise and rejectPromise are called, or more than once. Only the first call is valid, the rest is ignored.

        4. If the call to THEN throws an exception e,

          1. ifresolvepromiserejectPromiseIf it has already been called, ignore it.
          2. Otherwise, in order toeAs a reason to letpromiseChange to the Rejected state.
      4. If then is not a function, use x as value to make the promise become a fulfilled state.

    4. If x is not an object or function, use x as value to make the promise become a fulfilled state.

If a promise is resolved by an object in a loop thenable chain and the recursive nature of [[Resolve]](promise, thenable) causes it to be called again, it will be stuck in infinite recursion according to the algorithm above. While not mandatory, the algorithm encourages implementers to detect the presence of such recursions and reject promises with TypeError as reason.

Second, the implementation

  1. Implement the Promise class using class
  2. Use class private fields
  3. usequeueMicrotaskreplacesetTimeoutImplement asynchronous microtasks
// promise.js
/* Promise /A+ promise */
class Promise {
  static #FULFILLED = 'fulfilled'
  static #REJECTED = 'rejected'
  static #PENDING = 'pending'

  static #resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
      throw new TypeError('Chaining cycle detected for promise')}if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
      let called
      try {
        const then = x.then
        if (typeofthen ! = ='function') resolve(x)
        else {
          then.call(
            x,
            (value) = > {
              if (called) return
              called = true
              Promise.#resolvePromise(promise2, value, resolve, reject)
            },
            (reason) = > {
              if (called) return
              called = true
              reject(reason)
            }
          )
        }
      } catch (e) {
        if (called) return
        called = true
        reject(e)
      }
    } else {
      resolve(x)
    }
  }

  #state = Promise.#PENDING
  #result = null
  #onResolveCallbacks = []
  #onRejectCallbacks = []

  constructor(executor) {
    if (typeofexecutor ! = ='function') {
      return new TypeError(`Promise resolver ${executor} is not a function`)}try {
      executor(this.#resolve.bind(this), this.#reject.bind(this))}catch (e) {
      this.#reject(e)
    }
  }

  #resolve(value) {
    if (this.#state === Promise.#PENDING) {
      this.#state = Promise.#FULFILLED
      this.#result = value
      this.#onResolveCallbacks.forEach((cb) = > cb())
    }
  }
  #reject(reason) {
    if (this.#state === Promise.#PENDING) {
      this.#state = Promise.#REJECTED
      this.#result = reason
      this.#onRejectCallbacks.forEach((cb) = > cb())
    }
  }
  then(onFulfilled, onRejected) {
    if (typeofonFulfilled ! = ='function') {
      onFulfilled = (value) = > value
    }
    if (typeofonRejected ! = ='function') {
      onRejected = (reason) = > {
        throw reason
      }
    }
    const promise2 = new Promise((resolve, reject) = > {
      if (this.#state === Promise.#PENDING) {
        this.#onResolveCallbacks.push(() = > {
          queueMicrotask(() = > {
            try {
              const x = onFulfilled(this.#result)
              Promise.#resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
        this.#onRejectCallbacks.push(() = > {
          queueMicrotask(() = > {
            try {
              const x = onRejected(this.#result)
              Promise.#resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
      }
      if (this.#state === Promise.#FULFILLED) {
        queueMicrotask(() = > {
          try {
            const x = onFulfilled(this.#result)
            Promise.#resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if (this.#state === Promise.#REJECTED) {
        queueMicrotask(() = > {
          try {
            const x = onRejected(this.#result)
            Promise.#resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
    })
    return promise2
  }
}

// Implement the following functions and implement NPX Promises -aplus-tests
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) = > {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

module.exports = Promise
Copy the code

Three, validation,

  1. Save the file aspromise.js
  2. Run the following command in the directory to verify
$ npx promises-aplus-tests promise.js
Copy the code

Reference:

  1. juejin.cn/post/700177…