写一个简单的Promise

53 阅读5分钟

异步执行的诀窍

为了简化模型,我们在例子中只讨论 resolvereject 方法的实现思路与 resolve 类似。

简化版的异步执行由两个部分组成:resolvethen 。我们先来看一个标准的Promise:

const p = new Promise((resolve) => {
    setTimeout(() => {
        resolve(1)
    }, 2000)
})

p.then(res => console.log(res))

因为 resolve 在函数 setTimeout 中,因此它会延迟执行,而 then 方法是一个普通函数,它会同步执行,要想在 then 方法中获取 resolve 延迟执行的结果,有一个技巧是then 方法中的函数存储起来而不是立刻执行,等计时器到时,再用 resolve 执行这个函数

根据这个思路,我们定义一个类 MyPromise ,将 then 方法存储到 this.callback 变量中。

class MyPromise {
    constructor(fn) {
        this.callback = null
        fn(this._resolve) 
    }

    _resolve =(val) =>  {
        this.callback(val)
    }

    then = (onfulfilled) => {
        this.callback = onfulfilled
    }
}

const p = new MyPromise((resolve) => {
    setTimeout(() => {
        resolve(1)
    }, 2000)
})

p.then(res => console.log(res)) // 1

我们会同步执行 MyPromise 中的函数,因此我们会在 class 的初始化阶段 constructor 执行它。执行时我们会为它提供内部定义的 _resolve 函数,它的责任是接收用户提供的参数,取出存储的 this.callback 函数并用这个参数去执行存储中的方法函数。

可以这样写链式调用吗?

关于链式调用,最直接的想法是当我们调用一个类方法时,返回一个 class 自身,比如:

class Adder {
    value = 0;

    add = (val) => {
        this.value += val
        return this
    }

    getValue = () => {
        return this.value
    }
}

const adder = new Adder()

const value = adder
    .add(1)
    .add(2)
    .add(3)
    .getValue()

console.log(value); // 6

如果根据这个思路,我们可以在调用 .then 方法时返回 class 自身,从而调用下一个 then 方法,我们可以将这些方法全部存储到一个数组 this.callbacks 中。 在执行 resolve 时将 this.callbacks 数组中每个函数的执行结果传递给下一个函数。于是我们的代码看起来会像这样:


class MyPromise {
    constructor(fn) {
        this.callbacks = []
        this.value = null
        fn(this._resolve)
    }

    _resolve = (val , key = 0) =>{
        if(key === this.callbacks.length - 1){
            return this.callbacks[key](val)
        }else{
            const res = this.callbacks[key](val) 
            return this._resolve(res , key + 1)
        }

    }

    then = (onfulfilled) => {
        this.callbacks.push(onfulfilled)
        return this
    }
}

const p = new MyPromise((resolve) => {
    setTimeout(() => {
        resolve(1)
    }, 2000)
})

p
    .then(res => res + 1)
    .then(res => res + 1)
    .then(res => console.log(res)) // 3

可以这样写吗?当然不行!我们使用 Promise 是执行异步方法的,就像这样:

//使用Promise
function getUserId(url) {
    return new Promise(function (resolve) {
        //异步请求
        http.get(url, function (id) {
            resolve(id)
        })
    })
}
getUserId('some_url').then(function (id) {
    //do something
    return getNameById(id); // 新的Promise
}).then(function (name) {
    //do something
    return getCourseByName(name); // 新的Promise
}).then(function (course) {
    //do something
})

我们上面的写法只能支持同步函数的链式调用,如果遇到一个新的Promise ,它对这样的东西完全没有处理能力:

p
    .then(res => new MyPromise((resolve) => {
        setTimeout(() => {
            resolve(res + 1)
        }, 2000)
    }))
    .then(res => res + 1)
    .then(res => console.log(res))

    // [object Object]1
    // c:\Users\lemon\Downloads\Telegram Desktop\b.js:13
    //             const res = this.callbacks[key](val) 
    //                                            ^
    
    // TypeError: this.callbacks[key] is not a function
    //     at _resolve (c:\Users\lemon\Downloads\Telegram Desktop\b.js:13:44)
    //     at Timeout._onTimeout (c:\Users\lemon\Downloads\Telegram Desktop\b.js:34:13)
    //     at listOnTimeout (node:internal/timers:559:17)
    //     at processTimers (node:internal/timers:502:7)

我们会先尝试去修改这种写法,但很快会发现无从下手,我们只能推翻重来,首先我们需要让then方法返回一个新的Promise对象。

让then方法返回一个新的Promise对象

让then方法返回一个新的Promise对象,新的Promise对象上拥有自身的 then 方法,因此通过这种方法可以不断进行链式调用,这是我们改写后的 then 方法:

    then = (onfulfilled) => {
        return new MyPromise((resolve) => {
            this.callback = { onfulfilled, resolve }
        })
    }

这串代码比较让人疑惑的是我们除了要异步执行的 onfulfilled 函数外,同时将新 Promise 的 resolve 方法也加入了存储行列。

resolve 方法有两个功能:

  • 接收上一个Promise resolve 出的结果
  • 用该结果执行当前 Promise 的 callback 函数

也就是说 resolve 是链接两个 Promise 的关键,我们获取新 Promise 的 resolve 函数是用其接收 onfulfilled 的执行结果,将该结果传递给下一个 Promise . 因此引出了 resolve 函数的定义:

 _resolve = (value) => {
	const { onfulfilled, resolve } = this.callback
	const res = onfulfilled(value)
	resolve(res)
}

因为最后一个 then 方法创建的新 Promise 没有相应的 then 方法,因此没有定义自身的 callback 方法,所以在执行最后一个 resolve 时需要对空的 callback 做一下处理:

    _resolve = (value) => {
        if (!this.callback) return
        const { onfulfilled, resolve } = this.callback
        const res = onfulfilled(value)
        resolve(res)
    }

现在我们来试一下同步的 then 调用,它应该是可以正常工作的:


class MyPromise {
    constructor(fn) {
        this.callback = null
        fn(this._resolve)
    }


    _resolve = (value) => {
        if (!this.callback) return
        const { onfulfilled, resolve } = this.callback
        const res = onfulfilled(value)
        resolve(res)
    }

    then = (onfulfilled) => {
        return new MyPromise((resolve) => {
            this.callback = { onfulfilled, resolve }
        })
    }

}

const p = new MyPromise((resolve) => {
    setTimeout(() => {
        resolve(1)
    }, 2000)
})

p
    .then(res => res + 1)
    .then(res => res + 1)
    .then(res => console.log(res)) // 3

但这不是我们的最终目的,我们还需要兼容异步的 then 方法调用,比如如下的形式:

p
    .then(res => new MyPromise((resolve) => {
        setTimeout(() => {
            resolve(res + 1)
        }, 2000)
    }))
    .then(res => console.log(res))

和刚才的区别是,当异步方法执行完成后, onfulfilled 方法会返回一个 Promise 对象而非一个普通数据类型。根据我们刚才讨论的,这个 Promise 对象会被新Promise的 resolve 所接收。因此我们在 resolve 方法中对这种情况进行单独处理:

    _resolve = (value) => {
        if (!this.callback) return
		
		if (value instanceof MyPromise) {
			...
		}
		
        const { onfulfilled, resolve } = this.callback
        const res = onfulfilled(value)
        resolve(res)
    }

我们要做的,仅仅只是通过 then 方法取出这个返回的 Promise 对象中真正 resolve 出的值,然后用它走和之前一样的逻辑:

    _resolve = (value) => {
        if (value instanceof MyPromise) {
            const p = value;
            p.then((val) => {
                this.exec(val)
            })
            return
        }
        if (!this.callback) return
        this.exec(value)
    }

    exec = (value) => {
        const { onfulfilled, resolve } = this.callback
        const res = onfulfilled(value)
        resolve(res)
    }

完整代码:

class MyPromise {
    constructor(fn) {
        this.callback = null
        fn(this._resolve)
    }


    _resolve = (value) => {
        if (value instanceof MyPromise) {
            const p = value;
            p.then((val) => {
                this.exec(val)
            })
            return
        }
        if (!this.callback) return
        this.exec(value)
    }

    exec = (value) => {
        const { onfulfilled, resolve } = this.callback
        const res = onfulfilled(value)
        resolve(res)
    }

    then = (onfulfilled) => {
        return new MyPromise((resolve) => {
            this.callback = { onfulfilled, resolve }
        })
    }

}

const p = new MyPromise((resolve) => {
    setTimeout(() => {
        resolve(1)
    }, 2000)
})

p
    .then(res => new MyPromise((resolve) => {
        setTimeout(() => {
            resolve(res + 1)
        }, 2000)
    }))
    .then(res => console.log(res)) // 2

上面代码执行的流程图:

MyPromise.png