纯新手向,从零实现Promise (一)

153 阅读2分钟

很多手写 Promise 的文章喜欢直接罗列要点,一步到位实现完整代码,虽全面但不够友好,初学者往往难以消化。为此,我选择记录自己的理解,以循序渐进的方式,一步步实现 Promise,并详细讲解每个环节的设计原因,帮助大家轻松上手。

本文重点在于如何手动实现 Promise,关于其使用场景和意义,这里不再赘述。希望通过这篇文章,你能深入理解 Promise 的实现机制。

我们在开发过程中免不了使用一些异步获取数据相关的操作,下面将以最简单的一个模拟获取网络请求的例子作为切入点:

function request(fn) {
    setTimeout(() => {
        fn('success') 
    }, 1000) 
}

request((res) => { // 把回调函数作为参数传给reuest方法
    console.log(res); // 一秒后打印了success
})

我们把上面的回调函数的形式改成Promise的形式:

function requestFactory() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('success')
        }, 1000)
    })
}

const request = requestFactory()

request.then(res => {
    console.log(res) // 一秒后也打印了success
})

如何实现上述Promise操作呢,不难发现then方法是Promise内部执行的,我们只需要把这个方法缓存起来就可以了

class IPromise {
    constructor(executor) {
        this.onResolveCallback = null // 用于缓存then传进来的方法
        executor(this.resolve)
    }

    resolve = (value) => { // value => 传进来的success
        this.onResolveCallback(value)
    }

    then = (onFulfilled) => {
        this.onResolveCallback = onFulfilled
    }
}

function requestFactory() {
    return new IPromise(resolve => { // 这个回调函数作为executor传入IPromise
        setTimeout(() => {
            resolve('success')
        }, 1000)
    })
}

request.then(res => {
    console.log(res) // 一秒后打印了success
})

好了,最简单的Promise已经实现了,根据Promise的特性,then是可以多次调用的,咱们这种实现形式会有一个问题

const request = requestFactory()

request.then(res => {
    console.log('res1', res) // 被覆盖了
})

request.then(res => {
    console.log('res2', res) // 1秒后打印了 res2 success
})

这是后面注册的回调函数把前面的覆盖掉了,这时候我们就可以使用数组来存储这些回调函数

class IPromise {
    constructor(excutor) {
        this.onResolveCallbacks = [] // 使用数组来保存回调函数
        excutor(this.resolve)
    }

    resolve = (value) => {
        this.onResolveCallbacks.forEach(resolve => resolve(value)) // 遍历调用保存的回调
    }

    then = (onFulfilled) => {
        this.onResolveCallbacks.push(onFulfilled)
    }
}

同理,reject注册的回调函数也可以这样处理,不过还需要加入一个捕获错误

class IPromise {
        constructor(excutor) {
            this.onResolveCallbacks = [] // 成功的回调
            this.onRejectCallbacks = [] // 失败的回调
            try {
                excutor(this.resolve, this.reject) // 这里只能捕获同步错误
            } catch (error) {
                this.reject(error)
            }
        }

        resolve = (value) => {
            queueMicrotask(() => { // 需要放到微任务队列里面
                this.onResolveCallbacks.forEach(resolve => resolve(value))
            })
        }

        reject = (reason) => {
            queueMicrotask(() => { // 不放到微任务队列里的话,错误都捕获不到
                this.onRejectCallbacks.forEach(reject => reject(reason))
            })
        }

        then = (onFulfilled, onRejected) => {

            // 防止参数传空的情况
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
            onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }

            this.onResolveCallbacks.push(onFulfilled)
            this.onRejectCallbacks.push(onRejected)
        }
    }

    function requestFactory() {
        return new IPromise((res, rej) => {
            // throw new Error('error') 这样抛的错,reject需要放到异步去执行
            setTimeout(() => {
                rej(new Error('error')) // 试了下像上面的直接抛错,捕获不了,需要显式执行reject才行
            }, 1000)
        })
    }

    const request = requestFactory()

    request.then((res) => {
        console.log(res)
    }, (error) => {
        console.log('error', error);
    })

代码越来越多了,贪多嚼不烂,下一章说一说怎么做到链式调用。