17

0. Preface

Interviewer: "You write a promise."

Me: "I'm sorry to disturb you, bye!"

Now the front end is getting more and more curly, and I can't write by hand Promise I'm embarrassed to interview (manual dog head.jpg). Although not many people will implement Promise in their business, but the process of implementing Promise will make you understand Promise better, and there will be problems. can be investigated better.

If you are not familiar with Promises, it is recommended to take a look at the MDN documentation first.

Before implementing Promises, I recommend that you read the Promises/A+ specification (Chinese translation: Promise A+ specification ), and I will not introduce related concepts again in this article . It is recommended that you read the original English first, only high school level English knowledge is enough, and you can read the translation if you don’t understand it.

In addition, this article will use ES6 Class to achieve Promise . In order to facilitate everyone to compare with the Promise/A+ specification, the following order will be written in the order of the specification.

Before the official start, we create a new project with any name, and follow the steps below to initialize:

  • Open CMD or VS Code, run npm init , initialize the project
  • Create a new PromiseImpl.js file, and all the code implementations that follow are written in this file

Complete code address: ashengtan/promise-aplus-implementing

1. Terminology

In this part, you should just look at the specification directly, and there is nothing to explain.注意value的描述, value thenable (有then方法的对象或函数) Promise , which will be reflected in later implementations.

2. Requirements

2.1 State of Promises

One Promise has three states:

  • pending : initial state
  • fulfilled : successfully executed
  • rejected : Refused to execute

A Promise once changed from pending to fulfilled or rejected cannot be changed to any other state of dcc7f19. When fulfilled , an immutable value needs to be given; similarly, when rejected , an immutable reason needs to be given.

Based on the above information, we define 3 constants to represent the status of Promise :

 const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'

Next, we first define the basic framework of Promise , here I use ES6 Class to define:

 class PromiseImpl {
  constructor() {}

  then(onFulfilled, onRejected) {}
}

Here we first recall the basic usage of Promise :

 const promise = new Promise((resolve, reject) => {
  // ...do something
  resolve(value) // or reject(error)
})

// 多次调用
const p1 = promise.then()
const p2 = promise.then()
const p3 = promise.then()

Well, continue to improve PromiseImpl , first improve the construction method:

 class PromiseImpl {
  constructor() {
    // `Promise` 当前的状态,初始化时为 `pending`
    this.status = STATUS_PENDING
    // fulfilled 时的值
    this.value = null
    // rejected 时的原因
    this.reason = null
  }
}

In addition, we also need to define two methods for the callback when fulfilled and rejected :

 class PromiseImpl {
  constructor() {
    // ...其他代码

    // 2.1.2 When `fulfilled`, a `promise`:
    //  2.1.2.1 must not transition to any other state.
    //  2.1.2.2 must have a value, which must not change.
    const _resolve = value => {
      // 如果 `value` 是 `Promise`(即嵌套 `Promise`),
      // 则需要等待该 `Promise` 执行完成
      if (value instanceof PromiseImpl) {
        return value.then(
          value => _resolve(value),
          reason => _reject(reason)
        )
      }
      
      if (this.status === STATUS_PENDING) {
        this.status = STATUS_FULFILLED
        this.value = value
      }
    }

    // 2.1.3 When `rejected`, a `promise`:
    //  2.1.3.1 must not transition to any other state.
    //  2.1.3.2 must have a reason, which must not change.
    const _reject = reason => {
      if (this.status === STATUS_PENDING) {
        this.status = STATUS_REJECTED
        this.reason = reason
      }
    }
  }
}

注意_resolve()中, valuePromise的话( Promise ), Promise Execution is complete.这点很重要,因为后面的其他API 如Promise.resolvePromise.allPromise.allSettled等均需要等待嵌套---fcdfc5e444195b2d3a11f3a0f89cebd8 Promise才results will be returned.

Finally, don't forget that at new Promise() we need to pass resolve and reject to the caller:

 class PromiseImpl {
  constructor(executor) {
    // ...其他代码

    try {
      executor(_resolve, _reject)
    } catch (e) {
      _reject(e)
    }
  }
}

Use trycatch to wrap executor , because this part is the caller's code, we can't guarantee that the caller's code will not make mistakes.

2.2 Then method

A Promise must provide a then method, which accepts two parameters:

 promise.then(onFulfilled, onRejected)
 class PromiseImpl {
  then(onFulfilled, onRejected) {}
}

2.2.1 onFulfilled and onRejected

From Specification 2.2.1 we can learn the following information:

  • onFulfilled and onRejected are optional parameters
  • MUST be ignored if onFulfilled and onRejected are not functions

So, we can do it like this:

 class PromiseImpl {
  then(onFulfilled, onRejected) {
    // 2.2.1 Both `onFulfilled` and `onRejected` are optional arguments:
    //   2.2.1.1 If `onFulfilled` is not a function, it must be ignored
    //   2.2.1.2 If `onRejected` is not a function, it must be ignored
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {}
    onRejected = typeof onRejected === 'function' ? onRejected : () => {}
  }
}

2.2.2 onFulfilled Features

From Specification 2.2.2 we can learn the following information:

  • If onFulfilled is a function, it must be called after fulfilled , the first parameter is the value of promise
  • can only be called once
 class PromiseImpl {
  then(onFulfilled, onRejected) {
    // ...其他代码
    
    // 2.2.2 If `onFulfilled` is a function:
    //   2.2.2.1 it must be called after `promise` is fulfilled,
    // with promise’s value as its first argument.
    //   2.2.2.2 it must not be called before `promise` is fulfilled.
    //   2.2.2.3 it must not be called more than once.
    if (this.status === STATUS_FULFILLED) {
      onFulfilled(this.value)
    }
  }
}

2.2.3 onRejected Features

Same as onFulfilled , but it is called when rejected , the first parameter is promise the reason for failure

 class PromiseImpl {
  then(onFulfilled, onRejected) {
    // ...其他代码

    // 2.2.3 If onRejected is a function:
    //   2.2.3.1 it must be called after promise is rejected,
    // with promise’s reason as its first argument.
    //   2.2.3.2 it must not be called before promise is rejected.
    //   2.2.3.3 it must not be called more than once.
    if (this.status === STATUS_REJECTED) {
      onRejected(this.reason)
    }
  }
}

2.2.4 Asynchronous execution

In daily development, we often use Promise to do some asynchronous operations. Specification 2.2.4 is to specify the problem of asynchronous execution. The specific can be read in conjunction with the comments in the specification. The key is to ensure onFulfilled onRejected to execute asynchronously.

It should be pointed out that the specification does not stipulate that Promise must be implemented with the micro-task mechanism, so you can use the macro-task mechanism to implement it. Of course, the current browser uses micro-task to achieve. Here for convenience, we use setTimeout (belongs to macro-task ) to achieve.

Therefore, we need to slightly modify the above code:

 class PromiseImpl {
  then(onFulfilled, onRejected) {
    // ...其他代码
    
    // fulfilled
    if (this.status === STATUS_FULFILLED) {
      setTimeout(() => {
        onFulfilled(this.value)
      }, 0)
    }

    // rejected
    if (this.status === STATUS_REJECTED) {
      setTimeout(() => {
        onRejected(this.reason)
      }, 0)
    }
  }
}

2.2.5 onFulfilled and onRejected must be called as functions

This has already been implemented above.

2.2.6 then can be called multiple times

for example:

 const promise = new Promise((resolve, reject) => {
  // ...do something
  resolve(value) // or reject(error)
})

promise.then()
promise.then()
promise.catch()

因此, Promise fulfilled rejected时, onFulfilled或---546bf528107be179dd18320549a606b4 onRejected callback. Remember when we first defined resolve and reject ? Here we need to modify it to ensure that all callbacks are executed:

 const invokeArrayFns = (fns, arg) => {
  for (let i = 0; i < fns.length; i++) {
    fns[i](arg)
  }
}

class PromiseImpl {
  constructor(executor) {
    // ...其他代码

    // 用于存放 `fulfilled` 时的回调,一个 `Promise` 对象可以注册多个 `fulfilled` 回调函数
    this.onFulfilledCbs = []
    // 用于存放 `rejected` 时的回调,一个 `Promise` 对象可以注册多个 `rejected` 回调函数
    this.onRejectedCbs = []

    const resolve = value => {
      if (this.status === STATUS_PENDING) {
        this.status = STATUS_FULFILLED
        this.value = value
        // 2.2.6.1 If/when `promise` is fulfilled, 
        // all respective `onFulfilled` callbacks must execute 
        // in the order of their originating calls to `then`.
        invokeArrayFns(this.onFulfilledCbs, value)
      }
    }

    const reject = reason => {
      if (this.status === STATUS_PENDING) {
        this.status = STATUS_REJECTED
        this.reason = reason
        // 2.2.6.2 If/when `promise` is rejected, 
        // all respective `onRejected` callbacks must execute 
        // in the order of their originating calls to `then`.
        invokeArrayFns(this.onRejectedCbs, reason)
      }
    }
  }
}

Seeing this, you may have questions, when to store the corresponding callbacks in onFulfilledCbs and onRejectedCbs , the answer is when calling then :

 class PromiseImpl {
  then(onFulfilled, onRejected) {
    // ...其他代码

    // pending
    if (this.status === STATUS_PENDING) {
      this.onFulfilledCbs.push(() => {
        setTimeout(() => {
          onFulfilled(this.value)
        }, 0)
      })

      this.onRejectedCbs.push(() => {
        setTimeout(() => {
          onRejected(this.reason)
        }, 0)
      })
    }
  }
}

At this time Promise is in pending state, it is impossible to determine whether it is fulfilled or rejected , so the callback function needs to be stored. After confirmation, execute the corresponding callback function.

Note: invokeArrayFns comes from the source code in Vue.js 3.

2.2.7 then must return Promise

 promise2 = promise1.then(onFulfilled, onRejected)

So, in our code above, how can ---859c5af24d5b0da835e24684933bf2c9 then return Promise ? It's simple:

 class PromiseImpl {
  then(onFulfilled, onRejected) {
    let promise2 = new PromiseImpl((resolve, reject) => {
      if (this.status === STATUS_FULFILLED) {
        // ...相关代码
      }

      if (this.status === STATUS_REJECTED) {
        // ...相关代码
      }

      if (this.status === STATUS_PENDING) {
        // ...相关代码
      }
    })

    return promise2
  }
}

Since calling then then returns a new Promise object, we can also chain calls:

 Promise.resolve(42).then().then()...

2.2.7.1 ~ 2.2.7.4 These four points are more important, let's take a look at them separately.

2.2.7.1如果onFulfilled onRejected返回一个值xPromise[[Resolve]](promise2, x)

Explanation: In fact, the so-called operation Promise the solution process is to perform an operation. We extract this operation into a method and name it: promiseResolutionProcedure(promise, x, resolve, reject) . For convenience, we transparently transmit resolve and reject together.

 class PromiseImpl {
  then(onFulfilled, onRejected) {
    // ...其他代码
    
    let promise2 = new PromiseImpl((resolve, reject) => {
      if (this.status === STATUS_FULFILLED) {
        setTimeout(() => {
          // 2.2.7.1
          let x = onFulfilled(this.value)
          promiseResolutionProcedure(promise2, x, resolve, reject)
        }, 0)
      }

      if (this.status === STATUS_REJECTED) {
        setTimeout(() => {
          // 2.2.7.1
          let x = onRejected(this.reason)
          promiseResolutionProcedure(promise2, x, resolve, reject)
        }, 0)
      }

      if (this.status === STATUS_PENDING) {
        this.onFulfilledCbs.push(() => {
          setTimeout(() => {
            // 2.2.7.1 
            let x = onFulfilled(this.value)
            promiseResolutionProcedure(promise2, x, resolve, reject)
          }, 0)
        })

        this.onRejectedCbs.push(() => {
          setTimeout(() => {
            // 2.2.7.1
            let x = onRejected(this.reason)
            promiseResolutionProcedure(promise2, x, resolve, reject)
          }, 0)
        })
      }
    })

    return promise2
  }
}

2.2.7.2如果onFulfilled onRejected c2b20314c47a4562304d60ae2e4e3eba---抛出一个异常epromise2 rejected ,并返回Reason e .

解释: onFulfilled onRejected trycatch catch reject(e) .

 class PromiseImpl {
  then(onFulfilled, onRejected) {
    // ...其他代码
    
    let promise2 = new PromiseImpl((resolve, reject) => {
      if (this.status === STATUS_FULFILLED) {
        setTimeout(() => {
          try {
            // 2.2.7.1
            let x = onFulfilled(this.value)
            promiseResolutionProcedure(promise2, x, resolve, reject)
          } catch (e) {
            // 2.2.7.2
            reject(e)
          }
        }, 0)
      }

      if (this.status === STATUS_REJECTED) {
        setTimeout(() => {
          try {
            // 2.2.7.1
            let x = onRejected(this.reason)
            promiseResolutionProcedure(promise2, x, resolve, reject)
          } catch (e) {
            // 2.2.7.2
            reject(e)
          }
        }, 0)
      }

      if (this.status === STATUS_PENDING) {
        this.onFulfilledCbs.push(() => {
          setTimeout(() => {
            try {
              // 2.2.7.1
              let x = onFulfilled(this.value)
              promiseResolutionProcedure(promise2, x, resolve, reject)
            } catch (e) {
              // 2.2.7.2
              reject(e)
            }
          }, 0)
        })

        this.onRejectedCbs.push(() => {
          setTimeout(() => {
            try {
              // 2.2.7.1
              let x = onRejected(this.reason)
              promiseResolutionProcedure(promise2, x, resolve, reject)
            } catch (e) {
              // 2.2.7.2
              reject(e)
            }
          }, 0)
        })
      }
    })

    // 2.2.7 `then` must return a promise
    return promise2
  }
}

2.2.7.3 onFulfilled不是函数且---45bb07f82f5177121def731843f0cd1e promise1 fulfilledpromise2 fulfilled返回与promise1 same value.

Explanation: Transparent transmission of values, for example:

 Promise.resolve(42).then().then(value => console.log(value)) // 42

In the first then , we ignore onFulfilled , then in the chain call, we need to pass the value to the following then :

 class PromiseImpl {
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value

    // ...其他代码
  }
}

2.2.7.4 onRejected不是promise1 4e0c0b867f32400fa58a0ce0bbeeeb57---已经rejectedpromise2 rejected返回与promise1 Same reason.

Explanation: In the same way, the reason should also be transparently transmitted:

 Promise.reject('reason').catch().catch(reason => console.log(reason)) // 'reason'
 class PromiseImpl {
  then(onFulfilled, onRejected) {
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

    // ...其他代码
  }
}

2.3 Promise resolution process

Promise The solution process is an abstract operation, input a promise and a value, here we name it promiseResolutionProcedure(promise, x, resolve, reject) , and pass in resolve and reject two methods are used to call when fulfilled and rejected respectively.

2.3.1 If promise and x are the same object

If promise and x are the same object, reject the promise with reason TypeError .

 const promiseResolutionProcedure = (promise, x, resolve, reject) => {
  // 2.3.1 If `promise` and `x` refer to the same object,
  // reject `promise` with a `TypeError` as the reason
  if (promise === x) {
    return reject(new TypeError('`promise` and `x` refer to the same object, see: https://promisesaplus.com/#point-48'))
  }

  // ...其他代码
}

2.3.2 If x is a Promise object

If x is a Promise object, you need to execute recursively:

 const promiseResolutionProcedure = (promise, x, resolve, reject) => {
  // ...其他代码

  // 2.3.2 If `x` is a promise, adopt its state:
  //   2.3.2.1 If `x` is pending, `promise` must remain pending until `x` is fulfilled or rejected.
  //   2.3.2.2 If/when `x` is fulfilled, fulfill `promise` with the same value.
  //   2.3.2.3 If/when `x` is rejected, reject `promise` with the same reason.
  if (x instanceof PromiseImpl) {
    return x.then(
      value => promiseResolutionProcedure(promise, value, resolve, reject),
      reason => reject(reason)
    )
  }
}

2.3.3 if x is an object or function

If x is an object or function:

2.3.3.1 Assign x.then to x

Explanation: This is done for two purposes:

  • Avoid multiple accesses to x.then (this is also a small trick in daily development. When accessing the same attribute of an object multiple times, we usually use a variable to store the attribute to avoid multiple prototype chain lookup)
  • The value of x.then may be changed during execution
 const promiseResolutionProcedure = (promise, x, resolve, reject) => {
  // ...其他代码

  // 2.3.3 Otherwise, if x is an object or function
  if ((x !== null && typeof x === 'object') || typeof x === 'function') {
    
    // 2.3.3.1 Let `then` be `x.then`
    let then = x.then
  }
}

2.3.3.2 x.then fa745974bebd744931344d3889d3f5c5---取值时抛出异常epromise ,原因e

 const promiseResolutionProcedure = (promise, x, resolve, reject) => {
  // ...其他代码

  if ((x !== null && typeof x === 'object') || typeof x === 'function') {
    
    try {
      // 2.3.3.1 Let `then` be `x.then`
    } catch (e) {
      // 2.3.3.2 If retrieving the property `x.then` results in a thrown exception `e`,
      // reject `promise` with `e` as the reason.
      reject(e)
    }
  }
}

2.3.3.3 if then is a function

If then is a function, then x then the scope of ---46c6b394ae17662482e7a824139ee62f--- and call then The first is named resolvePromise and the second is named rejectPromise .

Explanation: It means to specify its this value when calling then x , and need to pass in two callback functions at the same time. At this time, it is best to use call() to achieve:

 then.call(x, resolvePromise, rejectPromise)

The meanings of 2.3.3.3.1 ~ 2.3.3.3.4 are summarized as follows:

  • 2.3.3.3.1 If resolvePromise is called, recursively call promiseResolutionProcedure with value y . Because Promise can be nested in Promise :

     then.call(
      x,
      y => promiseResolutionProcedure(promise, y, resolve, reject),
      rejectPromise
    )
  • 2.3.3.3.2如果rejectPromiserPromise ,原因r

     then.call(
      x,
      y => promiseResolutionProcedure(promise, y, resolve, reject), // resolvePromise
      r => reject(r) // rejectPromise
    )
  • 2.3.3.3.3 If resolvePromise and rejectPromise are called, or are called multiple times with the same parameter, only the first call is performed

    Explanation: Here we can solve it by setting a flag bit, and then make judgments in the two callback functions of resolvePromise and rejectPromise respectively:

     // 初始化时设置为 false
    let called = false
    
    if (called) {
        return
    }
    
    // `resolvePromise` 和 `rejectPromise` 被调用时设置为 true
    called = true
  • 2.3.3.3.4调用then异常e时, resolvePromise rejectPromise ,则忽略; Otherwise reject the Promise with reason e
 const promiseResolutionProcedure = (promise, x, resolve, reject) => {
  // ...其他代码
  
  let called = false

  if ((x !== null && typeof x === 'object') || typeof x === 'function') {
    try {
      let then = x.then

      if (typeof then === 'function') {
        // 2.3.3.3 If `then` is a function, call it with `x` as `this`,
        // first argument `resolvePromise`, and second argument `rejectPromise`
        then.call(
          // call it with `x` as `this`
          x,

          // `resolvePromise`
          // 2.3.3.3.1 If/when `resolvePromise` is called with a value `y`,
          // run `[[Resolve]](promise, y)`.
          y => {
            // 2.3.3.3.3 If both `resolvePromise` and `rejectPromise` are called,
            // or multiple calls to the same argument are made,
            // the first call takes precedence, and any further calls are ignored.
            if (called) {
              return
            }
            called = true

            promiseResolutionProcedure(promise, y, resolve, reject)
          },

          // `rejectPromise`
          // 2.3.3.3.2 If/when `rejectPromise` is called with a reason `r`,
          // reject `promise` with `r`
          r => {
            // 2.3.3.3.3
            if (called) {
              return
            }
            called = true

            reject(r)
          }
        )
      } else {
        // 2.3.3.4 If `then` is not a function, fulfill `promise` with `x`
        resolve(x)
      }
    } catch (e) {
      // 2.3.3.3.3
      if (called) {
        return
      }
      called = true

      // 2.3.3.2 If retrieving the property `x.then` results in a thrown exception `e`,
      // reject `promise` with `e` as the reason.

      // 2.3.3.3.4 If calling `then` throws an exception `e`
      //   2.3.3.3.4.1 If `resolvePromise` or `rejectPromise` have been called, ignore it
      //   2.3.3.3.4.2 Otherwise, reject `promise` with `e` as the reason

      reject(e)
    }
  }
}

2.3.3.4 If then is not a function, execute this promise , the parameter is x

 const promiseResolutionProcedure = (promise, x, resolve, reject) => {
  // ...其他代码

  if ((x !== null && typeof x === 'object') || typeof x === 'function') {
    try {
      let then = x.then

      if (typeof then === 'function') {
        // 2.3.3.3
      } else {
        // 2.3.3.4 If `then` is not a function, fulfill `promise` with `x`
        resolve(x)
      }
    } catch (e) {
    }
  }
}

2.3.4 If x is neither an object nor a function

If x is neither an object nor a function, execute this promise with parameters x :

 const promiseResolutionProcedure = (promise, x, resolve, reject) => {
  // ...其他代码

  if ((x !== null && typeof x === 'object') || typeof x === 'function') {
     // 2.3.3
  } else {
    // 2.3.4 If `x` is not an object or function, fulfill `promise` with `x`
    resolve(x)
  }
}

So far, our customization Promise has been completed. Here is the source code: promise-aplus-implementing .

4. How to test

The Promise/A+ specification provides a test script: promises-tests , which you can use to test that your implementation conforms to the specification.

Add the following code in PromiseImpl.js :

 // PromiseImpl.js

const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'

const invokeArrayFns = (fns, arg) => {
  // ...相关代码
}

const promiseResolutionProcedure = (promise, x, resolve, reject) => {
  // ...相关代码
}

class PromiseImpl {
  // ...相关代码
}

PromiseImpl.defer = PromiseImpl.deferred = () => {
  const dfd = {}
  dfd.promise = new PromiseImpl((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })

  return dfd
}

module.exports = PromiseImpl

Then add ---8a525d9c53163209cf47d074432e8cc4 package.json in scripts :

 // package.json

{
  "scripts": {
    "test": "promises-aplus-tests PromiseImpl.js"
  }
}

Then, run npm run test to execute the test script, if your implementation conforms to the Promise/A+ specification, all test cases will pass.

5. Implementation of other APIs

The Promise/A+ specification only specifies the implementation of the then() method. Other methods such as catch() and finally() are not included in the specification. But in terms of implementation, these methods can be implemented by secondary encapsulation of the then() method or other methods.

In addition, methods such as all() , race() and so on, the parameter is an iterable object, such as Array , Set Map etc. So, what is an iterable object According to the specification in ES6, to be an iterable object, an object must implement the @@iterator method, that is, the object must have a property named @@iterator , Usually we use the constant Symbol.iterator to access this property.

According to the above information, to determine whether a parameter is an iterable object, the implementation is as follows:

 const isIterable = value => !!value && typeof value[Symbol.iterator] === 'function'

Promise.resolve

Promise.resolve(value) , static method , its parameters are as follows:

  • The parameter is Promise object
  • Arguments are thenable object (object with then() method)
  • The argument is a primitive value or an object without a then() method
  • parameter is empty

Therefore, its return value is determined by its parameters: it may be a specific value, or it may be a Promise object:

 class PromiseImpl {
  static resolve(value) {
    // 1. 参数是 Promise 对象
    if (value instanceof PromiseImpl) {
      return value
    }

    // 2. 参数是 thenable
    if (value !== null && typeof value === 'object' && typeof value.then === 'function') {
      return new PromiseImpl((resolve, reject) => {
        value.then(
          v => resolve(v),
          e => reject(e)
        )
      })
    }

    // 3. 参数是原始值或不具有 `then()` 方法的对象
    // 4. 参数为空
    return new PromiseImpl((resolve, reject) => resolve(value))
  }
}

Promise.reject

Promise.reject(reason) , static method , the parameter is Promise the reason for refusing to execute, and returns a Promise object, the state is rejected

 class PromiseImpl { 
  static reject(reason) {
    return new PromiseImpl((resolve, reject) => reject(reason))
  }
}

Promise.prototype.catch

Promise.prototype.catch(onRejected) is actually the syntactic sugar of then(null, onRejected) :

 class PromiseImpl {
  catch(onRejected) {
    return this.then(null, onRejected)
  }
}

Promise.prototype.finally

As the name suggests, no matter Promise the final result is fulfilled or rejected , finally will be executed.

 class PromiseImpl {
  finally(onFinally) {
    return this.then(
      value => PromiseImpl.resolve(onFinally()).then(() => value), 
      reason => PromiseImpl.resolve(onFinally()).then(() => { throw reason })
    )
  }
}

Promise.all

Promise.all(iterable) , static method , the parameter is an iterable object:

  • Only when all iterable in Promise are successfully executed, will fulfilled , the return value of the callback function is all Promise The formed array, in the same order as iterable .
  • Once there is a Promise that refuses to execute, the status is rejected , and the reason for the first Promise 0a77bf93e218d3dd4565989a7c5ccef8--- that refuses to execute is used as the return value of the callback function.
  • This method returns a Promise .
 class PromiseImpl {
  static all(iterable) {
    if (!isIterable(iterable)) {
      return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
    }

    return new PromiseImpl((resolve, reject) => {
      // `fulfilled` 的 Promise 数量
      let fulfilledCount = 0
      // 收集 Promise `fulfilled` 时的值
      const res = []
  
      for (let i = 0; i < iterable.length; i++) {
        const iterator = iterable[i]
        iterator.then(
          value => {
            res[i] = value
            fulfilledCount++
            if (fulfilledCount === iterable.length) {
              resolve(res)
            }
          },
          reason => reject(reason)
        )
      }
    })
  }
}

have a test:

 const promise1 = Promise.resolve(42)
const promise2 = new PromiseImpl((resolve, reject) => setTimeout(() => resolve('value2'), 1000))

PromiseImpl.all([
  promise1,
  promise2
]).then(values => console.log('values:', values))

result:

 values: [42, 'value2']

It seems perfect, but is it really so? Looking closely at our code, what if iterable is an empty array? What if iterable contains Promise ? like this:

 PromiseImpl.all([])
PromiseImpl.all([promise1, promise2, 'value3'])

In this case, executing the previous code will not get any result. Therefore, the code needs to be improved again:

 class PromiseImpl {
  static all(iterable) {
    if (!isIterable(iterable)) {
      return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
    }

    return new PromiseImpl((resolve, reject) => {
      // `fulfilled` 的 Promise 数量
      let fulfilledCount = 0
      // 收集 Promise `fulfilled` 时的值
      const res = []
      
      // - 填充 `res` 的值
      // - 增加 `fulfilledCount`
      // - 判断所有 `Promise` 是否已经全部成功执行
      const processRes = (index, value) => {
        res[index] = value
        fulfilledCount++
        if (fulfilledCount === iterable.length) {
          resolve(res)
        }
      }

      if (iterable.length === 0) {
        resolve(res)
      } else {
        for (let i = 0; i < iterable.length; i++) {
          const iterator = iterable[i]
          
          if (iterator && typeof iterator.then === 'function') {
            iterator.then(
              value => processRes(i, value),
              reason => reject(reason)
            )
          } else {
            processRes(i, iterator)
          }
        }
      }
    })
  }
}

Now try again:

 const promise1 = PromiseImpl.resolve(42)
const promise2 = 3
const promise3 = new PromiseImpl((resolve, reject) => setTimeout(() => resolve('value3'), 1000))

PromiseImpl.all([
  promise1,
  promise2,
  promise3,
  'a'
]).then(values => console.log('values:', values))
// 结果:values: [42, 3, 'value3', 'a']

PromiseImpl.all([]).then(values => console.log('values:', values))
// 结果:values: []

Promise.allSettled

Promise.allSettled(iterable) , static method , the parameter is an iterable object:

  • When all iterable in Promise are successfully executed or rejected, the return value is an object array.
  • If there is nesting Promise , you need to wait for that Promise to complete.
  • Returns a new Promise object.

For allSettled , we can encapsulate it on the basis of all :

 class PromiseImpl {
  static allSettled(iterable) {
    if (!isIterable(iterable)) {
      return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
    }

    const promises = []
    for (const iterator of iterable) {
      promises.push(
        PromiseImpl.resolve(iterator).then(
          value => ({ status: STATUS_FULFILLED, value }),
          reason => ({ status: STATUS_REJECTED, reason })
        )
      )
    }

    return PromiseImpl.all(promises)
  }
}

Test Results:

 PromiseImpl.allSettled([
  PromiseImpl.resolve(42),
  PromiseImpl.reject('Oops!'),
  PromiseImpl.resolve(PromiseImpl.resolve(4242)),
  'a'
]).then(values => console.log(values))
// 结果:
// [
//   { status: 'fulfilled', value: 42 },
//   { status: 'rejected', reason: 'Oops!' },
//   { status: 'fulfilled', value: 4242 },
//   { status: 'fulfilled', value: 'a' }
// ]

Promise.race

Promise.race(iterable) , static method , the parameter is an iterable object:

  • When any one of iterable Promise successfully executes or refuses to execute, use this Promise the value returned successfully or the reason for refusal to execute
  • If iterable is empty, it is in pending state
  • Returns a new Promise object

E.g:

 Promise.race([
  Promise.resolve(42),
  Promise.reject('Oops!'),
  'a'
]).then(values => console.log(values))
  .catch(reason => console.log(reason))
// 结果:42

Promise.race([
  Promise.reject('Oops!'),
  Promise.resolve(42),
  'a'
]).then(values => console.log(values))
  .catch(reason => console.log(reason))
// 结果:Oops!

The implementation is as follows:

 class PromiseImpl {
  static race(iterable) {
    if (!isIterable(iterable)) {
      return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
    }

    return new PromiseImpl((resolve, reject) => {
      if (iterable.length === 0) {
        return
      } else {
        for (let i = 0; i < iterable.length; i++) {
          const iterator = iterable[i]
          
          if (iterator && typeof iterator.then === 'function') {
            iterator.then(
              value => resolve(value),
              reason => reject(reason)
            )
            return
          } else {
            resolve(iterator)
            return
          }
        }
      }
    })
  }
}

One thing to note here: don't forget to use return to end the for loop .

Test Results:

 PromiseImpl.race([
  PromiseImpl.resolve(42),
  PromiseImpl.reject('Oops!'),
  'a'
]).then(values => console.log(values))
  .catch(reason => console.log(reason))
// 结果:42

PromiseImpl.race([
  PromiseImpl.reject('Oops!'),
  PromiseImpl.resolve(42),
  'a'
]).then(values => console.log(values))
  .catch(reason => console.log(reason))
// 结果:'Oops!'

PromiseImpl.race([
  'a',
  PromiseImpl.reject('Oops!'),
  PromiseImpl.resolve(42)
]).then(values => console.log(values))
  .catch(reason => console.log(reason))
// 结果:'a'

6. Discuss together

  1. In 2.3.3.1 assigning x.then to then , under what circumstances will the pointer of x.then be changed?
  2. In 2.3.3.3 if then is a function , is there any other way to achieve it besides using call() ?

7. Summary

The realization Promise is basically divided into three steps:

  1. Define the state of Promise
  2. Implement then method
  3. Realization Promise solving process

8. Write at the end

In the past, I was concerned about whether I could implement a Promise , and I looked for articles everywhere, this code Ctrl+C , that code Ctrl+V . Now, what I value is the implementation process, in which you will not only become more familiar with Promise but also learn how to turn the specification into actual code step by step. Doing the right thing is far more important than doing the right thing .

If you think this article is helpful to you, please: like , favorite, forward ; if you have any questions, please write it in the comment area, and we will discuss it together.

renew

Thanks for the reminder, the implementation of Promise.resolve and Promise.allSettled is wrong, and the article has been updated.

References


Seng
121 声望552 粉丝

主业:Web 前端开发,兴趣爱好:读书、爬山