手写Promise

978 阅读3分钟

今天来玩一下Promise,一步步分析。

  • 原版Promise效果
let promise = new Promise((resolve, reject) => resolve("success"));

promise.then(msg => console.log(msg));

控制台调试结果

  • 自定义一个Pomise
class MyPromise {

    constructor(func) {
        this.callbacks = []

        // 绑定this指向
        const resolve = this.resolve.bind(this)
        func(resolve)
    }

    resolve(e) {
        this.callbacks.forEach(cb => cb(e))
    }

    reject() {
        console.error('reject')
    }

    then(cb) {
        this.callbacks.push(cb)
    }
}

new MyPromise((resolve, _) => setTimeout(() => resolve('success'), 0)).then(e => console.log(e))
  • 执行结果 执行结果

  • 此时看起来似乎实现了功能,但是当new MyPromise的时候把异步方法变成同步方法,此时then方法里无法打印出值,如:

new MyPromise((resolve, _) => resolve('success')).then(e => console.log(e))
  • 此时应该想办法把then方法里的函数放在resolve方法执行之前,所以采取浏览器事件触发线程,把事件添加到待处理JS任务队列的队尾,等待js引擎的处理,让resolve执行的时候then方法的回调函数已经存在回调数组callbacks中,如setTimeout方法。这里就有人疑问了,不是刚去掉吗,怎么又加上哈哈。。。的确,但是加的位置不一样了。不过这里加的是微任务queueMicrotask,和setTimeout宏任务比起来微任务执行优先级更高,如:
class MyPromise {

    constructor(func) {
        this.callbacks = []

        // 绑定this指向
        const resolve = this.resolve.bind(this)

        // 此处加上queueMicrotask微任务方法
        queueMicrotask(() => func(resolve))
    }

    resolve(e) {
        this.callbacks.forEach(cb => cb(e))
    }

    reject() {
        console.error('reject')
    }

    then(cb) {
        this.callbacks.push(cb)
    }
}

new MyPromise((resolve, _) => resolve('success')).then(e => console.log(e))
  • 熟悉的结果回来了。要是执行中途出现异常,那就捕获异常并返回reject,如:
try {
      queueMicrotask(() => func(resolve))
} catch (error) {
      this.reject()
}
  • 接着有人问,这里只有一个then方法,那么多个then方法怎么办呢?好办,链式调用,在then方法中return this,如:
then(cb) {
    this.callbacks.push(cb)

    // 链式调用
    return this
}
  • 输出代码
new MyPromise((resolve, _) => resolve('success')).then(e => console.log(e)).then(e => console.log(e))
  • 执行结果 执行结果

  • 这样看着可以执行多个then方法了,可是还有问题,细心的小伙伴发现了then方法执行后的结果每次都是一样的,并没有随着上次执行的变化而变化,如: 执行结果

  • 两次then方法中取到的数据都是1。所以要把then方法返回的值保存一下

class MyPromise {

    constructor(func) {
        // 初始化值
        this.value = null;

        this.callbacks = []

        // 绑定this指向
        const resolve = this.resolve.bind(this)
        queueMicrotask(() => func(resolve))
    }

    resolve(e) {
        this.value = e
        this.callbacks.forEach(cb => {
            let newValue = cb(this.value)

            // 把then方法返回的值存起来
            this.value = newValue
        })
    }

    reject() {
        console.error('reject')
    }

    then(cb) {
        this.callbacks.push(cb)
        return this
    }
}
  • 执行结果 image.png

  • 然后还有小伙伴说,当最后一次then方法是个耗时好操作怎么办,只会输出一个数值,如: 执行结果

  • 这里遵循Promise源码的规定,在Promise源码中是有状态这一说的,有种状态,pending - 进行中fulfilled - 成功rejected - 失败。当主体执行完后,把状态变更一下,后续再执行直接把后续加上的then方式执行即可,所以实现一下,如:

// 状态对象
const stateObj = {
    PENDING: 'pending',
    FULFILLED: 'fulfilled',
    REJECTED: 'rejected'
}

class MyPromise {

    constructor(func) {

        // 初始化状态
        this.state = stateObj.PENDING

        // 初始化值
        this.value = null;

        this.callbacks = []

        // 绑定this指向
        const resolve = this.resolve.bind(this)

        try {
            queueMicrotask(() => func(resolve))
        } catch (error) {
            this.reject()
        }
    }

    resolve(e) {
        this.value = e

        // 改变状态
        this.state = stateObj.FULFILLED
        this.callbacks.forEach(cb => {
            let newValue = cb(this.value)

            // 把then方法返回的值存起来
            this.value = newValue
        })
    }

    reject() {
        console.error(stateObj.REJECTED)
    }

    then(cb) {
        if (this.state === stateObj.PENDING) {
            this.callbacks.push(cb)
            return this
        }

        try {
            // 后续调用直接执行传进来的方法
            cb(this.value)
        } catch (error) {
            this.reject()
        }
        return this
    }
}

const mp = new MyPromise((resolve, _) => resolve(2)).then(e => {
    console.log(e)
    return e * 2
})

setTimeout(() => {
    mp.then(e => {
        console.log(e)
        return e * 2
    })
}, 3000)
  • 执行结果又回来了 执行结果

好咧,今天就玩到这里,后续继续优化