Skip to content

从0到1手写一个符合Promise A+规范的Promise类

本篇文章,我会一步一步带大家从头实现一个符合Promise A+ 规范的 Promise类,认真阅读本篇文章,并自己跟着步骤实现一遍后,相信你会对Promise有更深一步的了解。你将会学习到下面几点内容。

  1. promise的出现,解决前端的哪些问题
  2. promise的执行机制
  3. .then(...).catch()
  4. promise为什么可以链式执行.then().then().then()
  5. 手写Promise.race()Promise.all()`

术语

  1. promise 是一个有 then 方法的对象或者是函数,行为遵循Promise A+规范。
  2. thenable 是一个有 then 方法的对象或者是函数。
  3. value 是 promise 状态成功时的值,也就是resolve的参数,包括各种数据类型,也包括undefined/thenable或者是promise
  4. reason 是 promise 状态失败时的值,也就是reject的参数,表示拒绝的原因
  5. exception 是一个使用throw抛出的异常值

规范

Promise States

promise有三种状态,要注意他们之间的流转关系

  1. pending

    1.1 初始状态,可改变

    1.2 一个promise在resolve或者rejected前都处于这个状态

    1.3 可以通过 resolve -> fulfilled 状态

    1.4 可以通过 reject -> rejected 状态

  2. fulfilled

    1.1 最终态,不可改变

    1.2 一个promise被resolve后改变成这个状态

    1.3 必须拥有一个value值,

    (注意:如果直接resolve(),那么这个value值是undefined )

  3. rejected

    1.1 最终态,不可改变

    1.2 一个promise被rejected后改变成这个状态

    1.3 必须拥有一个reason值,

    (注意:如果直接reject(),那么这个value值是undefined )

Tips:总结一下就是,promise的状态流转是这样的

pending -> resolve(value) -> fulfilled

pending -> reject(reason) -> rejected

一步一步实现promise

第一步:先有一个类MyPromise

肯定需要先有一个类,因为平时使用promise的时候,都是 new Promse(...),所以第一步,先声明一个类,这里我们叫MyPromise,在构造方法中,有三个属性,statusvaluereason

js
// 声明状态常量
const PENDING = 'pending' // 默认状态
const FULFILLED = 'fulfilled' // resolve()之后的状态 
const REJECTED = 'rejected' // reject() 之后的状态


class MyPromise {
    constructor() {
        this.status = PENDING // 默认状态
        this.value = null
        this.reason = null
    }
}

第二步:构造器增加参数fn

是不是少了点啥,平时使用promise时,new Promise((resolve, reject)=>{}),构造方法接收的参数是一个函数,没有错,少的就是参数,我们把参数加上

js
class MyPromise {
    constructor(fn) {
        this.status = PENDING // 默认状态
        this.value = null
        this.reason = null
        // 在Promise A+ 规范中 在初始化promise的时候,就需要执行这个函数,所以我们直接执行一下
        fn()
    }
}

在执行函数fn时,需要处理一下参数resolvereject,并且需要注意执行fn的时候,并不确定这个函数里面有什么逻辑,可能会有错误,所以应该使用try...catch...包裹一下,改造后如下

js
class MyPromise {
    constructor(fn) {
        this.status = PENDING // 默认状态
        this.value = null
        this.reason = null
        /**
         * 注意:在初始化promise的时候,就需要执行这个函数,并且有任何报错都要通过reject抛出去
         *  所以,执行的时候用try...catch...包裹一下,使用bind绑定当前的this,防止出现乱起八糟的情况
         */
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (e) {
            this.reject(e) // 报错的时候,直接reject
        }
    }
    
    // 状态由pending->fulfilled
    resolve(value) {}
    
    // 状态由pending->rejected
    reject(reason) {}
}

第三步:resolvereject方法

上面我们已经处理过了入参fn,接下来就应该着重看一下fn的两个参数resolvereject,这两个参数,其实就是promise的两个回调函数,用来改变状态和返回结果,我们来写一下这两个函数的逻辑

js
class MyPromise {
    constructor(fn) {/** ... */}
    
    // 参数value是外界给传过来的
     /**
     * @description promise的resolve,成功时执行
     * 做了两件事
     *  1、设置自己的 value
     *  2、将状态改为 FULFILLED
     * 状态有两种流转方式,所以只有当前状态是 PENDING 时才允许改变,一旦改变不可逆
     *  pending -> resolve(value) -> fulfilled    
     *  pending -> reject(reason) -> rejected
     * 
     * @param {*} value 从外面传过来的值,可以是任意类型
     */
    resolve(value) {
        if (this.status === PENDING) {
            this.value = value
            this.status = FULFILLED
        }
    }
    // 参数reason是外界给传过来的
    reject(reason) {
        if (this.status === PENDING) {
            this.reason = reason
           	this.status = REJECTED
        }
    }
    
	/**
     * 比如我们封装一个jQuery ajax请求时(就简单的做个比方)
     * getData(){
     *  return new Promise((resolve, reject)=>{
     *      $.ajax(
     *          url: 'xxx',
     *          success: function(data){
     *              // 这里就拿到了这个data,然后就可以执行promise的回调resolve将这个结果返回出去
     *			   // 这时就触发了Promise的resolve回调,
     *              resolve(data)
     *          },
     *          error: function(error){
     *              // 失败就可以reject出去这个错误
     *              reject(error)
     *          },
     * 
     *      )
     *  })
     * }
     * 
     */
}

第四步:then(onFulfilled, onRejected)方法

接下来我们就开始研究一下这个then方法了,在平常使用promise时,return new Promise().then(fn1,fn2).then方法接收两个参数,第一个参数是成功时的回调,第二个是失败时的回调,注意,这两个回调只会执行一个(咱举个例子试试)

image-20210928234313934image-20210928234534427

不管怎么试,最后的结果都是一样的,这两个回调,只会执行一个,所以咱开始写一下then函数

js
class MyPromise {
    constructor(fn) {/** ... */}
    
    resolve(value) {/** ... */}
    reject(reason) {/** ... */}
    
    /**
     * onFulfilled 成功时的回调,外面传过来的
     * onRejected 失败时的回调,外面传过来的
     * 还拿刚才封装的ajax请求来说,我们把服务器数据拿过来之后,就可以做我们想做的事了
     * getData().then(
     *  (value)=>{
     *      // 数据拿到了,做我们想做的事
     *      this.xxxData = value
     *  },
     *  (reason)=>{
     *      // 失败的回调,这时候我们一般会弄个弹窗,提示一下用户出了什么错
     *      // 比如,网络超时、断网、未授权、等等等等
     *  }
     * )
     * 
     *   
     */
    then(onFulfilled, onRejected) {
        // 调用完then方法,返回值仍然是一个promise,所以我们再new一个promise2,用来返回,这样就达到了链式调用的效果
        // .then().then().then().then().then().... 想多少都行,无穷无尽的链式操作
        const promise2 = new MyPromise((resolve, reject) => { 
            switch (this.status) {
                // 状态为 FULFILLED 时,调用一下成功的回调
                case FULFILLED:
                    onFulfilled()
                    break;

                // 状态为 REJECTED 时,调用一下失败的回调    
                case REJECTED:
                    onRejected()
                    break;

                // 状态为 PENDING 时,怎么办?????下一步再我们再研究
                case PENDING:
                    // ...下一步再再研究
                    break;
            }
        })
        return promise2
    }
}

第五步:分析promise的两种调用方式

(分析)众所周知,我们在使用promise时,一般会有两种调用方式

  • 方式一:直接在刚声明的promise对象链式调用then方法,这种链式调用,当结果被resolve后,会自动触发then方法的回调,所以会把resolve的结果打印出来(reject同理),比如

    js
    const test = new Promise((resolve, reject)=>{
        setTimeout(()=>{
           resolve(111) 
        }, 1000)
        // 直接链式调用,刚声明出来就调用,如果状态是fulfilled或者rejected时,会自动触发对应的回调,然后就可以在回调函数里操作我们其他的业务逻辑
    }).then((value)=>{
        console.log(value)
        console.log(test)
    })
  • 方式二:同步调用then方法,先声明,然后同步调用,同步调用的时候,resolve的结果由于延迟了一秒才会执行,所以在同步执行then方法的时候,所有的状态都是pending,这时候就需要考虑两件事了,(1)调用then方法时,是pending状态怎么处理?(2)如何监听到状态的改变,然后去触发then方法对应的回调函数?

    js
    const test = new Promise((resolve, reject)=>{
        setTimeout(()=>{
           resolve(111) 
        }, 1000)
        // 直接链式调用,刚声明出来就调用
    })
    
    test.then((value)=>{console.log(value)})
    test.then((value)=>{console.log(value)})
    test.then((value)=>{console.log(value)})
    test.then((value)=>{console.log(value)})
    
    console.log(test)

第六步:改写then方法,使用gettersetter监听状态的改变

所以针对第四步的问题,和第五步分析中考虑的两个问题,来继续改写我们的代码,

  • 对于第五步的第一个问题,(1)调用then方法时,是pending状态怎么处理?

    可以声明两个数组,将未执行的回调先存起来,等到状态改变时,再挨个从数组拿出来执行一遍。

  • 对于第五步的第二个问题,(2)如何监听到状态的改变,然后去触发then方法对应的回调函数?

    可以使用es6中的getter和setter来监听constructor中属性值变化,显示的声明出getter和setter方法,然后就可以从缓存回调的数组中取出回调函数,挨个执行一遍,这正好应对了步骤五中的方式二,当我们同步的调用then方法时,同一个promise同步调用多次then,每次调用时,状态都是pending,这时就将回调函数存缓存数组中,在状态发生改变时,在挨个执行,执行顺序与注册时的顺序正好是一致的

js
// 声明状态常量
/** ... */

class MyPromise {
    // 声明两个私有数组,缓存状态未执行的回调函数
    FULFILLED_CALLBACK_LIST = []
    REJECTED_CALLBACK_LIST = []
    /**
     * 定义私有变量 _status,其对应的属性是构造器中的status
     * 在使用getter、setter监听status时,我们操控 _status
     * 防止死循环
     * (
     *      如果不用一个私有变量存储,那么在每次 对 status进行赋值和取值的操作时,
     *      都会调用 setter和getter,这样就又是一波 赋值取值操作,然后就继续调用 setter和getter,
     *      如此循环往复,成了死循环。
     *      
     *      但是,当使用 _status 私有变量成员时,_status 并不会被getter、setter监听,所以就避免了死循环
     *      
     *  )
     */
    _status = PENDING

    constructor(fn) {/** ... */}
    resolve(value) {/** ... */}
    reject(reason) {/** ... */}

    /**
     * 监听状态的改变,如果变成了fulfilled或者rejected,
     * 就从缓存回调事件的数组中,把该执行的回调挨着执行一遍
     */
    set status(newStatus) {
        this._status = newStatus
        switch (newStatus) {
            case FULFILLED:
                this.FULFILLED_CALLBACK_LIST.forEach(callback => {
                    callback(this.value)
                });
                break;
            case REJECTED:
                this.REJECTED_CALLBACK_LIST.forEach(callback => {
                    callback(this.reason)
                });
                break;
        }
    }
	
	// 
    get status() {
        return this._status
    }

    then(onFulfilled, onRejected) {
        const promise2 = new MyPromise((resolve, reject) => {
            switch (this.status) {
                /** .... */
                case PENDING:
                    // 状态为 PENDING 时,我们就先把回调存起来,等状态改变之后再拿出来执行
                    this.FULFILLED_CALLBACK_LIST.push(onFulfilled)
                    this.REJECTED_CALLBACK_LIST.push(onRejected)
                    break;
            }
        })
        return promise2
    }
}

第七步:then方法参数校验,queueMicrotask包裹回调方法,转换到微任务队列

接下来继续完善then方法,先完善三个地方

  • (1)then(onFulfilled, onRejected)then方法中的onFulfilledonRejected是从外部接收的,需要校验参数,期望是函数
  • (2)onFulfilled, onRejected,函数的执行必需在微任务中,这里简单的使用queueMicrotask函数包裹一下
  • (3)onFulfilled, onRejected,函数的执行过程中,可能会报错,毕竟我们也不知道外部传过来的函数里面都会执行哪些业务,会报错很正常,所以需要使用try...catch...包裹一下,假如有报错,直接把错误reject(e)出去
js
// 声明状态常量
/** ... */
class MyPromise {
    /** ... */
    constructor(fn) {/** ... */}
    resolve(value) {/** ... */}
    reject(reason) {/** ... */}
    get status() {/** ... */}
    set status(newStatus) {/** ... */}

    then(onFulfilled, onRejected) {
        /**
         * 校验 onFulfilled, onRejected
         * 如果是函数,就还用自己
         * 如果不是函数,我们就忽略,给个默认的函数
         */
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
            // 给的默认函数需要有返回值,为什么需要有呢?在下面的步骤会给出分析
            return value
        }
        const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
            throw reason
        }

        const promise2 = new MyPromise((resolve, reject) => {
            // 使用queueMicrotask改造成微任务
            const fulfilledMicrotask = () => {
                queueMicrotask(() => {
                    // 使用catch包裹,一旦出错就reject出去
                    try {
                        realOnFulfilled(this.value)
                    } catch (e) {
                        reject(e)
                    }
                })
            }

            // 使用queueMicrotask改造成微任务
            const rejectedMicrotask = () => {
                queueMicrotask(() => {
                    // 使用catch包裹,一旦出错就reject出去
                    try {
                        realOnRejected(this.reason)
                    } catch (e) {
                        reject(e)
                    }
                })
            }

            switch (this.status) {
                case FULFILLED:
                    // 此处改为调用对应的微任务
                    fulfilledMicrotask()
                    break;
                case REJECTED:
                    // 此处改为调用对应的微任务
                    rejectedMicrotask()
                    break;
                case PENDING:
                    // 此处缓存数组改为存微任务
                    this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
                    this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
                    break;
            }
        })
        return promise2
    }

    // 判断参数是否为函数
    isFunction(param) {
        return typeof param === 'function'
    }
}

第八步:分析then(onFulfilled, onRejected)方法中回调方法的返回值,抛出疑问,声明resolvePromise(promise2, x, resolve, reject)方法

我们继续分析,除了第七步考虑的三点外,还有一个很重要的点需要考虑,在调用then(onFulfilled, onRejected)方法时,外部传来的函数onFulfilled, onRejected,很可能会有返回值,并且返回值会有很多种类型,先来抛出几点疑惑,

  • (1)如果返回值仍然是一个promise怎么处理?
  • (2)如果返回值是一个对象或者函数怎么处理?
  • (3)返回值就不是promise,也不是对象或者函数,那么就只能是基本类型了,这时候又怎么处理?

在这步我们先不管怎么处理,我们先把问题抛出来,写个处理的方法去处理,在第九步在专心考虑怎么处理这几种情况,所以接着第七步代码改写

js
// 声明状态常量
/** ... */

class MyPromise {
    /** ... */
    constructor(fn) {/** ... */}
    resolve(value) {/** ... */}
    reject(reason) {/** ... */}
    get status() {/** ... */}
    set status(newStatus) {/** ... */}
    
    then(onFulfilled, onRejected) {
        /** ... */
        const promise2 = new MyPromise((resolve, reject) => {
            const fulfilledMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        // 返回值我们定义为 x
                        const x = realOnFulfilled(this.value)
                        /**
                         * 处理返回值的方法,我们定义为 resolvePromise
                         * 
                         * 为什么参数会多传递个 当前的返回值promise2呢?
                         *  这是因为当返回值 x 也是一个Promise时,
                         *      把自己.then返回的promise2也传递过去,为了校验 x === promise2的情况,
                         *      如果它俩完全是同一个promise,会导致死循环
                         * */
                        this.resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            }
            const rejectedMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        // 返回值我们定义为 x
                        const x = realOnRejected(this.reason)
                        // 处理返回结果
                        this.resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            }
         	/** ... */
        })
        return promise2
    }

    // 处理回调函数中的返回值,会有大量的if操作,因为有很多种情况,都应该考虑到
    resolvePromise(promise2, x, resolve, reject) {

    }
    
    isFunction(param) {/** ... */}
}

第九步:剖析resolvePromise(promise2, x, resolve, reject)方法

接着第八步抛出的疑惑,开始完善resolvePromise(promise2, x, resolve, reject)方法的判断逻辑框架,由外到里,一点点剖析,分析得知,外部有四个if判断

  • (1)如果promise2 === x,即,调用.then()方法得到的promise与执行完回调函数的返回值x是同一个promise,这显然是不合常理的,因为遵循Promise A+规范的链式操作.then().then().then().then()...,每次.then()返回的结果都是一个新的promise对象,如果调用完某一个回调后,比如

    js
    let test = new Promise((resolve, reject)=>{
        resolve(111)
    }).then(
    	()=>{
            // 假设执行完这个回调后,由于一些意外的操作,返回值 x 与 执行完.then()后的返回值 是同一个promise
            // return promise2
        }
    )

    这样的话会导致死循环,因为如果相等的话就会按照返回值是promise来处理,会继续执行它的then()方法来递归解析返回值,但是每次返回的x都是这个promise,因而就导致了死循环,所以在最初阶段就需要判断一下,如果x === promise2,那么直接抛出类型异常。

  • (2)如果x instanceof MyPromise,即,执行完回调的返回值x仍然是一个promise,比如下面这个例子,可以看出来,.then()返回的promise继承了x的状态和结果,具体怎么实现,待会再看

    js
    let test = new Promise((resolve, reject)=>{
        resolve(111)
    }).then(
    	(value)=>{
            console.log(value)
            // 假设执行完这个回调后,又返回了一个Promise
            // 
            return new Promise((resolve, reject)=>{
                resolve(222)
            })
        }
    )

    image-20210929135310368

    • (3)如果回调的返回值x是一个对象或者是方法,类似于下面这个例子

      js
      let test = new Promise((resolve, reject)=>{
          resolve(111)
      }).then(
      	(value)=>{
              console.log(value)
              // 假设执行完这个回调后,又返回了一个对象
              // 
              return {
                  a: 'aaa',
                  b: 'bbb'
              }
          }
      )
      test.then(console.log)
      js
      let test = new Promise((resolve, reject)=>{
          resolve(111)
      }).then(
      	(value)=>{
              console.log(value)
              // 假设执行完这个回调后,又返回了一个对象
              // 
              return {
                  a: 'aaa',
                  b: 'bbb',
                  then: 'ccc'
              }
          }
      )
      test.then(console.log)
      js
      let test = new Promise((resolve, reject)=>{
          resolve(111)
      }).then(
      	(value)=>{
              console.log(value)
              // 假设执行完这个回调后,又返回了一个对象
              // 
              return {
                  a: 'aaa',
                  b: 'bbb',
                  then: (resolve, reject)=>{
                      resolve(2222)
                  }
              }
          }
      )
      test.then(console.log)

      image-20210929150528467

      js
      let test = new Promise((resolve, reject)=>{
          resolve(111)
      }).then(
      	(value)=>{
              console.log(value)
              // 假设执行完这个回调后,又返回了一个方法
              // 
              return ()=>{
                  console.log(2222)
              }
        }
      )

    test.then(console.log)

    
    运行完这几个例子,会发现,如果是对象,会找对象里有没有一个属性是`then`,看看符不符合`promise A+`中的then()方法的规范,即,`then((resolve, reject)=>{resolve(1111)})`,类似这样的方法,可以传成功回调和失败回调,如果有就会继承,没有的话就返回原结果`x`。
    
    这里有点绕,待会直接看代码理解一下,记得把例子敲一下试试。
    
    - (4)排除以上条件,剩下的只能是基本类型,如果是基本数据类型,直接返回结果

第十步:实现resolvePromise(promise2, x, resolve, reject)方法,解析回调函数返回的结果x

第九步已经将所有的条件都分析了一遍,现在开始写第八步解析回调结果x的方法resolvePromise(promise2, x, resolve, reject)

js
// 声明状态常量
/** ... */

class MyPromise {
    /** ... */

    constructor(fn) {/** ... */}
    resolve(value) {/** ... */}
    reject(reason) {/** ... */}
    get status() {/** ... */}
    set status(newStatus) {/** ... */}

    then(onFulfilled, onRejected) {
        /** ... */
        const promise2 = new MyPromise((resolve, reject) => {
            const fulfilledMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        // 返回值我们定义为 x
                        const x = realOnFulfilled(this.value)
                        this.resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            }
            const rejectedMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        // 返回值我们定义为 x
                        const x = realOnRejected(this.reason)
                        // 处理返回结果
                        this.resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            }
         	/** ... */
        })
        return promise2
    }

    // 处理回调函数中的返回值,会有大量的if操作,因为有很多种情况,都应该考虑到
    resolvePromise(promise2, x, resolve, reject) {
        // 1、如果 .then()返回的promise 和 执行完回调返回值是同一个promise
        if (x === promise2) {
            // 不能相等,如果相等会死循环,应保证每次执行完返回的promise的唯一性
            return reject(new TypeError('The promise and the return value are the same'))
        }

        // 2、如果返回值x是promise
        if (x instanceof MyPromise) {
            // 微任务中执行,继续解析Promise
            queueMicrotask(() => {
                x.then(
                    (y) => {
                        // 递归解析,一直解析成其他情况为止
                        this.resolvePromise(promise2, y, resolve, reject)
                    },
                    reject,
                )
            })
        } else if (typeof x === 'object' || this.isFunction(x)) { // 3、如果返回值是对象或者函数
            // 先判断是不是null
            if (x === null) {
                return resolve(x) // 直接返回结果
            }

            // 在看看对象里有没有符合promise规范的then方法
            let then = null
            try {
                // 把这个then 取出来,万一报错了,直接reject出去
                then = x.then
            } catch (e) {
                reject(e)
            }

            if (this.isFunction(then)) {
                // 成功和失败的回调只能调用一个
                let called = false
                // 执行方法的时候包裹一个try..catch... 捕获方法里的异常
                try {
                    // 如果是x.then是function
                    then.call(
                        x,
                        (y) => {
                            if (called) return
                            called = true
                            // 继续解析返回的结果
                            this.resolvePromise(promise2, y, resolve, reject)
                        },
                        (r) => {
                            if (called) return
                            called = true
                            // 直接reject回去
                            reject(r)
                        }
                    )
                } catch (e) {
                    if (called) return
                    called = true
                    // 直接reject回去
                    reject(r)
                }
            } else { // x是对象,且x.then不是function || x直接就是一个function
                resolve(x) // 直接resolve出去
            }
        } else { // 4、如果什么都不是,那只能是基本类型了,直接把结果resolve出去
            resolve(x)
        }
    }

    isFunction(param) {/** ... */}
}

第十一步:实现.catch()方法

promise除了可以使用.then(onFulfilled, onRejected)方法的第二个参数获取失败的回调结果外,还可以使用.catch()方法获得,比如

js
const test = new Promise((resolve, reject)=>{
    setTimeout(()=>{
       reject(111) 
    }, 1000)
}).then(
    value=>{},
    reason=>{
        console.log(reason, 'onRejected')
    }
)

// 或者
test.catch(reason=>{
    console.log(reason, 'onRejected')
})

我们自己也实现一个

js
// 声明状态常量
/** ... */

class MyPromise {
    /** ... */

    constructor(fn) {/** ... */}
    resolve(value) {/** ... */}
    reject(reason) {/** ... */}
    get status() {/** ... */}
    set status(newStatus) {/** ... */}
    
    // 直接调用then方法,不传成功的回调,只传失败的回调即可
    catch(onRejected) {
        return this.then(null, onRejected)
    }

    then(onFulfilled, onRejected) {/** ... */}

    resolvePromise(promise2, x, resolve, reject) {/** ... */}

    isFunction(param) {/** ... */}
}

通过上面的十步,我们已经可以使用自己定义的promise,自己可以代入用例试一试,比如

js
const test = new MyPromise((resolve, reject)=>{
    setTimeout(() => {
        resolve(111)
    }, 1000)
}).then(console.log)

console.log(test)

或者

js
const test = new MyPromise((resolve, reject)=>{
    setTimeout(() => {
        resolve(111)
    }, 1000)
})

test.then(console.log)
test.then(console.log)
test.then(console.log)
test.then(console.log)

第十二步:实现类的静态方法Promise.resolve()

在实际的工作中,我们有时还会直接使用promise的静态方法Promise.resolve(value)直接返回一个promise对象,存在两种情况

  • (1)如果value是一个基本类型,以这个基本类型为value,返回一个promise对象

    js
    const test = Promise.resolve(1111)
    test.then(console.log)
  • (2)如果value是一个promise对象,那直接返回这个promise

    js
    const test = Promise.resolve( 
    	new Promise((resolve, reject)=>{
            setTimeout(()=>{
                resolve(111)
            }, 1000)
        })
    )
    test.then(console.log)

我们自己也实现一个MyPromise.resolve()

js
// 声明状态常量
/** ... */

class MyPromise {
    /** ... */
    constructor(fn) {/** ... */}
    
    /**
     * 注意:
     *  静态成员方法只能通过类名调用
     *  并且在方法体里也只能调用静态方法,没有this
     */
    static resolve(value) {
        // 如果value是一个Promise,直接返回
        if (value instanceof MyPromise) {
            return value
        }

        // 如果不是就直接以value为值,返回一个新的Promise
        return new MyPromise((resolve) => {
            resolve(value)
        })
    }

    resolve(value) {/** ... */}
    reject(reason) {/** ... */}
    get status() {/** ... */}
    set status(newStatus) {/** ... */}
    then(onFulfilled, onRejected) {/** ... */}
    resolvePromise(promise2, x, resolve, reject) {/** ... */}
    isFunction(param) {/** ... */}
}

第十三步:实现类的静态方法Promise.reject()

Promise.reject(reason)直接返回一个fulfilled状态的promise,比如

js
const test = Promise.reject(1111)
test.then(
	(value)=>{console.log(value,'fulfilled')},
    (reason)=>{console.log(reason,'rejected')},
)

自己也实现一个

js
// 声明状态常量
/** ... */

class MyPromise {
    /** ... */
    constructor(fn) {/** ... */}
    static resolve(value) {/** ... */}
    
    static reject(reason) {
        // 以reason为值,返回一个新的Promise
        return new MyPromise((resolve, reject) => {
            reject(reason)
        })
    }

    resolve(value) {/** ... */}
    reject(reason) {/** ... */}
    get status() {/** ... */}
    set status(newStatus) {/** ... */}
    then(onFulfilled, onRejected) {/** ... */}
    resolvePromise(promise2, x, resolve, reject) {/** ... */}
    isFunction(param) {/** ... */}
}

第十四步:实现类的静态方法Promise.race()

promiserace方法,是传入一个数组(其实是类数组,只要可以被迭代都可以),数组里存了一堆等待执行的promise对象,如果不是promise,内部直接给全部转换为promise,我们并不知道这些``promie最终执行的结果,当使用Promise.race(arr).then()时,这些promise一旦有一个被resolve了,就直接将结果返回,执行时是同步的,可以使用for`循环来模拟,for循环默认为同步执行

js
// 声明状态常量
/** ... */

class MyPromise {
    /** ... */
    constructor(fn) {/** ... */}
    static resolve(value) {/** ... */}
    static reject(reason) {/** ... */}
    
    static race(iterableList) {
        return new MyPromise((resolve, reject) => {
            /** 
             * 判断传入的参数是否可迭代 
             * */
            if (!MyPromise.isIterable(iterableList)) {
                return reject(new TypeError(`${iterableList} is not iterable (cannot read property Symbol(Symbol.iterator))`))
            }

            // 2、将类数组转换为数组
            const promiseList = Array.from(iterableList)
            const promiseLength = promiseList.length

            // 如果是一个空数组,直接resolve一个空数组
            if (promiseLength === 0) {
                return resolve([])
            } else {
                // 3、同步执行数组中的Promise
                for (let i = 0; i < promiseLength; i++) {
                    // 4、为了防止某一个参数不是Promise,直接全转换一下
                    MyPromise.resolve(promiseList[i]).then(
                        (value) => {
                            // 5、一旦有结果了,直接返回
                            return resolve(value)
                        },
                        (reason) => {
                            // 5、
                            return reject(reason)
                        }
                    )
                }
            }
        })
    }

    /**
     * @description 判断value是否可迭代
     * @param {*} value 
     * @returns {Boolean} true:可迭代;false:不可迭代
     */
    static isIterable(value) {
        // 如果是空或undefined 直接返回false
        if (value === null || value === undefined) {
            return false
        } else {
            // 对象里如果没有Symbol.iterator,默认是不可迭代的
            // 可迭代的对象都会默认实现Symbol.iterator迭代器
            return !(value[Symbol.iterator] === undefined)
        }
    }


    resolve(value) {/** ... */}
    reject(reason) {/** ... */}
    get status() {/** ... */}
    set status(newStatus) {/** ... */}
    then(onFulfilled, onRejected) {/** ... */}
    resolvePromise(promise2, x, resolve, reject) {/** ... */}
    isFunction(param) {/** ... */}
}

第十五步:实现类的静态方法Promise.all()

promiseall方法与race方法的不同点是all方法只有所有结果都resolve时才会返回结果,一旦有一个reject就会走失败的回调

js
// 声明状态常量
/** ... */

class MyPromise {
    /** ... */
    constructor(fn) {/** ... */}
    static resolve(value) {/** ... */}
    static reject(reason) {/** ... */}
   
    static race(iterableList) {/** ... */}
    
    static all(iterableList) {
        return new MyPromise((resolve, reject) => {
            // 1、判断参数是否是可迭代的
            if (!MyPromise.isIterable(iterableList)) {
                return reject(new TypeError(`${iterableList} is not iterable (cannot read property Symbol(Symbol.iterator))`))
            }
            // 2、转换为数组
            const promiseList = Array.from(iterableList)
            const promiseLength = promiseList.length
            /**
             * 取一个计数器,每次执行resolve回调时就+1
             * 并声明一个返回值的数组,每次resolve时就将返回值存进去,
             * 只有当计数器的长度和promise个数相等时,即所有promise都成功执行了,
             * 然后就将存成功结果的数组resolve出去
             */
            let resolvedCount = 0
            let resolvedValues = new Array(promiseLength)
            // 如果是空数组,直接返回空数组
            if (promiseLength === 0) {
                return resolve([])
            } else {
                // 3、同步执行
                for (let i = 0; i < promiseLength; i++) {
                    // 4、全部转换为promise对象
                    MyPromise.resolve(promiseList[i]).then(
                        (value) => {
                            // 成功时就+1
                            resolvedCount++
                            // 5、存入对应的value到结果数组中
                            resolvedValues[i] = value
                            if (resolvedCount === promiseLength) {
                                return resolve(resolvedValues)
                            }
                        },
                        (reason) => {
                            // 6、一旦有失败的,就将失败结果reject出去
                            return reject(reason)
                        },
                    )

                }
            }
        })
    }
    
    static isIterable(value) {/** ... */}


    resolve(value) {/** ... */}
    reject(reason) {/** ... */}
    get status() {/** ... */}
    set status(newStatus) {/** ... */}
    then(onFulfilled, onRejected) {/** ... */}
    resolvePromise(promise2, x, resolve, reject) {/** ... */}
    isFunction(param) {/** ... */}
}

总结

以上就自己实现了一个自己的promise,可以试着敲一敲,代入一些用例试一试,总共实现了promise的以下内容

  1. new Promise()时都做了哪些事
  2. then方法内部的执行机制
  3. promise链式执行的原理
  4. .catch方法执行原理
  5. 手写promise.race()
  6. 手写promise.all()

源代码地址:https://gitee.com/hrbust_cheny/note_code/blob/master/ES6/promise/test9.js