Skip to content

实现一个promise

摘要

之前在网上看了很多关于promise的实现,但是多多少少都有问题,甚至有的实现还很容易误导读者对promise的理解,所以这里结合观察原生promise的行为来实现一个promise,尽可能的接近原生的promise,同时这也可能是全网最正确的实现方式 为了同时兼容nodejs和chrome,这里使用的是queueMicrotask,当然如果要想获得最好的浏览器端兼容性建议使用MutationObserver

1. 从Promise的基本用法入手

js
   const promise = new Promise((resolve, reject) => {
    // do something
      resolve('success')
   }).then((res) => {
    // do something
     console.log(res)
   }).catch((err) => {})

从上面的代码可以看出,Promise是一个类,构造函数接受一个函数作为参数,这个函数有两个参数,分别是resolve和reject,这两个参数也是函数,分别用来改变Promise的状态。

通俗的理解就是:传入一个带有resolve, reject两个参数的函数,我们可以在这个函数中做些事情,resolve, reject就是两个传话筒,当我们想告诉外界我们做完了,就可以调用resolve,当我们想告诉外界我们失败了,就可以调用reject。而then和catch方法就是用来向Promise注册解决了和拒绝之后要做什么。

根据上面的通俗理解,因此很容易想到Promise必须要有:

三种状态待解决已经解决拒绝解决,分别对应pending, fulfilled, rejected

一个状态变量_status,用来表示当前Promise是三个状态的哪一个。

两个事件列表变量_fulfilledQueues_rejectedQueues,分别用来储存解决了和拒绝之后要做的事情

一个值变量_value,用来储存resolve或者reject传入的值。

2. 开始实现Promise的构造函数

js
const isFunction = (variable) => typeof variable === 'function'
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
  constructor(handle){
   if (!isFunction(handle)) throw new Error('MyPromise must accept a function as a parameter')
   this._status = PENDING
   this._value = undefined
   this._fulfilledQueues = []
   this._rejectedQueues = []
   try {
    handle(this._resolve.bind(this), this._reject.bind(this))
   } catch (err) {
      this._reject(err)
   }
  }
}

3. 实现resolve和reject两个传话筒

实现之前 还是先观察原生的Promise的行为

js
const promise1 = new Promise((resolve, reject) => {
  resolve(1)
})
console.log(123)
console.log(promise1) 
/* 输出 :
123
Promise {<fulfilled>: 1}
*/

const promise2 = new Promise((resolve, reject) => {
  resolve(promise1)
})
const promise3 = new Promise((resolve, reject) => {
  resolve(promise2)
})
console.log(456)
console.log(promise3)
queueMicrotask(() => {
  console.log("第一层:",promise3)
  queueMicrotask(() => {
    console.log("第三层:",promise3)
    queueMicrotask(() => {
      console.log("第三层:",promise3)
    })
 })
})
/* 输出
123
Promise {<fulfilled>: 1}
456
Promise {<pending>}
第一层: Promise {<pending>}
第二层: Promise {<pending>}
第三层: Promise {<fulfilled>: 1}
*/

结论:resolve(非promise)执行会立即改变状态。resolve(Promise) 不会立刻改变状态,而是会等到传入的Promise状态改变之后再改变状态。也就是要等调用promise的then方法callback的时候才会执行promise2的resolve,过程是promise1.then(v=>promise2.resolve(v)) -> promise2.then(v=>promise3.resolve(v)),这三步都是微任务异步的,这也解释了为什么第三层promise3才被解决

js
const promise = new Promise((resolve, reject) => {
  resolve(1)
}).then((res) => {console.log("then called",res)})
console.log(123)
console.log(promise) 
queueMicrotask(() => {console.log(promise)})
/* 输出 :
123
Promise {<pending>}
then called 1
Promise {<fulfilled>: undefined}
*/

结论: resolve(1)执行会立即改变状态,但是then 会返回一个pending状态的的Promise,该promise会在微任务队列解决。

reject的行为比较简单这里就不累赘了,自己可以验证一下。开始实现resolve和reject

js
// 添加resovle时执行的函数
  _resolve (val) {
        if (this._status !== PENDING) return
        if (!(val instanceof this.constructor)) {
            this._status = FULFILLED
            this._value = val
        }
        const run = () => {
            // 依次执行成功队列中的函数,并清空队列
            const runFulfilled = (value) => {
                let cb
                while ((cb = this._fulfilledQueues.shift())) {
                    cb(value)
                }
            }
            /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
                当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
              */
            if (val instanceof this.constructor) {
                val.then(
                    (value) => {
                        this._resolve(value)
                    },
                    (err) => {
                        this._reject(err)
                    }
                )
            } else {
                runFulfilled(val)
            }
        }
        if (queueMicrotask) {
            queueMicrotask(run)
        } else {
            setTimeout(run, 0)
        }
    }
  _reject (err) {
      console.log(err, this);

      if (this._status !== PENDING) return
      this._status = REJECTED
      this._value = err
      // 依次执行失败队列中的函数,并清空队列
      const run = () => {
          let cb
          while ((cb = this._rejectedQueues.shift())) {
              cb(err)
          }
      }
      if (queueMicrotask) {
          queueMicrotask(run)
      } else {
          setTimeout(run, 0)
      }
  }

4.实现then方法

then的特点是返回一个新的promise,并且可以链式调用。then如果返回非promise的值,则新的promise状态为fulfilled,值为返回值。如果返回的是promise,则需要等待promise状态改变后,新的promise状态才会改变,且状态取决于参数Promsie对象的状态。

js
then (onFulfilled, onRejected) {
        const { _value, _status } = this
        return new this.constructor((onFulfilledNext, onRejectedNext) => {
            let fulfilled = (value) => {
                try {
                    if (!isFunction(onFulfilled)) {
                        onFulfilledNext(value)
                    } else {
                        let res = onFulfilled(value)
                        if (res instanceof this.constructor) {
                            res.then(onFulfilledNext, onRejectedNext)
                        } else {
                            onFulfilledNext(res)
                        }
                    }
                } catch (err) {
                    onRejectedNext(err)
                }
            }
            let rejected = (error) => {
                try {
                    if (!isFunction(onRejected)) {
                        onRejectedNext(error)
                    } else {
                        let res = onRejected(error)
                        if (res instanceof this.constructor) {
                            res.then(onFulfilledNext, onRejectedNext)
                        } else {
                            onFulfilledNext(res)
                        }
                    }
                } catch (err) {
                    onRejectedNext(err)
                }
            }
            switch (_status) {
                case PENDING:
                    this._fulfilledQueues.push(fulfilled)
                    this._rejectedQueues.push(rejected)
                    break
                case FULFILLED:
                    queueMicrotask(() => fulfilled(_value))
                    break
                case REJECTED:
                    queueMicrotask(() => rejected(_value))
                    break
            }
        })
    }
    // 添加catch方法
    catch (onRejected) {
        return this.then(undefined, onRejected)
    }

最后几个静态方法和factory方法就不写了,比较简单。 需要说明的一点是:

js
// 添加静态reject方法
    static reject (value) {
        return new this((resolve, reject) => reject(value))
    }

这里使用this而不是MyPromise,是因为MyPromise是类名,而this指向的是当前实例的构造函数,这样写可以使用call改变this。可以在任何实现与 Promise() 构造函数相同签名的构造函数上调用,如果写死MyPromise就不能实现调用了。例如下面是MDN上面的一个例子,我们可以在一个构造函数上调用它,并传入 console.log 作为 reject 参数:

js
class NotPromise {
  constructor(executor) {
    // “resolve”和“reject”函数的行为与原生 Promise 完全不同,但 `Promise.reject()` 方法以相同的方式调用它们。
    executor(
      (value) => console.log("已解决", value),
      (reason) => console.log("已拒绝", reason),
    );
  }
}

Promise.reject.call(NotPromise, "foo"); // 输出 "已拒绝 foo"

// 我们的MyPromise也可以这样调用
MyPromise.reject.call(NotPromise, "foo"); // 输出 "已拒绝 foo"
完整代码
js
const isFunction = (variable) => typeof variable === 'function'
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
    constructor(handle) {
        if (!isFunction(handle)) {
            throw new Error('MyPromise must accept a function as a parameter')
        }
        this._status = PENDING
        this._value = undefined
        this._fulfilledQueues = []
        this._rejectedQueues = []
        try {
            handle(this._resolve.bind(this), this._reject.bind(this))
        } catch (err) {
            this._reject(err)
        }
    }
    // 添加resovle时执行的函数
    _resolve (val) {
        if (this._status !== PENDING) return
        if (!(val instanceof this.constructor)) {
            this._status = FULFILLED
            this._value = val
        }
        const run = () => {
            // 依次执行成功队列中的函数,并清空队列
            const runFulfilled = (value) => {
                let cb
                while ((cb = this._fulfilledQueues.shift())) {
                    cb(value)
                }
            }
            /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
                当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
              */
            if (val instanceof this.constructor) {
                val.then(
                    (value) => {
                        this._resolve(value)
                    },
                    (err) => {
                        this._reject(err)
                    }
                )
            } else {
                runFulfilled(val)
            }
        }
        if (queueMicrotask) {
            queueMicrotask(run)
        } else {
            setTimeout(run, 0)
        }
    }
    _reject (err) {
        console.log(err, this);

        if (this._status !== PENDING) return
        this._status = REJECTED
        this._value = err
        // 依次执行失败队列中的函数,并清空队列
        const run = () => {
            let cb
            while ((cb = this._rejectedQueues.shift())) {
                cb(err)
            }
        }
        if (queueMicrotask) {
            queueMicrotask(run)
        } else {
            setTimeout(run, 0)
        }
    }
    then (onFulfilled, onRejected) {
        const { _value, _status } = this
        return new this.constructor((onFulfilledNext, onRejectedNext) => {
            let fulfilled = (value) => {
                try {
                    if (!isFunction(onFulfilled)) {
                        onFulfilledNext(value)
                    } else {
                        let res = onFulfilled(value)
                        if (res instanceof this.constructor) {
                            res.then(onFulfilledNext, onRejectedNext)
                        } else {
                            onFulfilledNext(res)
                        }
                    }
                } catch (err) {
                    onRejectedNext(err)
                }
            }
            let rejected = (error) => {
                try {
                    if (!isFunction(onRejected)) {
                        onRejectedNext(error)
                    } else {
                        let res = onRejected(error)
                        if (res instanceof this.constructor) {
                            res.then(onFulfilledNext, onRejectedNext)
                        } else {
                            onFulfilledNext(res)
                        }
                    }
                } catch (err) {
                    onRejectedNext(err)
                }
            }
            switch (_status) {
                case PENDING:
                    this._fulfilledQueues.push(fulfilled)
                    this._rejectedQueues.push(rejected)
                    break
                case FULFILLED:
                    queueMicrotask(() => fulfilled(_value))
                    break
                case REJECTED:
                    queueMicrotask(() => rejected(_value))
                    break
            }
        })
    }
    // 添加catch方法
    catch (onRejected) {
        return this.then(undefined, onRejected)
    }
    // 添加静态resolve方法
    static resolve (value) {
        // 如果参数是MyPromise实例,直接返回这个实例
        if (value instanceof this) return value
        return new this((resolve) => resolve(value))
    }
    // 添加静态reject方法
    static reject (value) {
        return new this((resolve, reject) => reject(value))
    }
    // 添加静态all方法
    static all (list) {
        return new this((resolve, reject) => {
            /**
             * 返回值的集合
             */
            let values = []
            let count = 0
            for (let [i, p] of list.entries()) {
                // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
                this.resolve(p).then(
                    (res) => {
                        values[i] = res
                        count++
                        // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
                        if (count === list.length) resolve(values)
                    },
                    (err) => {
                        // 有一个被rejected时返回的MyPromise状态就变成rejected
                        reject(err)
                    }
                )
            }
        })
    }
    // 添加静态race方法
    static race (list) {
        return new this((resolve, reject) => {
            for (let p of list) {
                // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
                this.resolve(p).then(
                    (res) => {
                        resolve(res)
                    },
                    (err) => {
                        reject(err)
                    }
                )
            }
        })
    }
    finally (cb) {
        return this.then(
            (value) => this.constructor.resolve(cb()).then(() => value),
            (reason) =>
                this.constructor.resolve(cb()).then(() => {
                    throw reason
                })
        )
    }
}