likes
comments
collection
share

Promise实现原理学习

作者站长头像
站长
· 阅读数 21

Promise实现原理学习

从"Promise化"一个API谈起

熟悉微信小程序开发的读者应该知道, 我们在微信小程序环境中发送一个网络请求时会使用wx.request(). 参考官方文档, 具体用法如下:

wx.request({
    url: 'test.php',
    data: { a:1 },
    header: {
        'content-type': 'application/json'
    },
    success (res) {
        console.log(res.data)
    }
})

这样的设计有一个小问题, 就是很容易出现"回调地狱"问题. 如果我们想先通过./userInfo接口来获取登录用户信息数据, 再从登录用户信息数据中通过请求./${id}/firendList接口来获取登录用户的所有好友列表, 那么就需要执行如下代码:

wx.request({
    url: './userInfo',
    success (res) {
        const id = res.data.id
        wx.request({
            url: './${id}/friendList',
            success (res) {
                console.log(res)
            }
        })
    }
})

上面代码虽然只是嵌套了一层回调而已, 但足以说明问题.

我们知道解决"回调地狱"问题的一个很好的工具就是Promise, 所以上面代码Promise化, 代码会变成下面所示的样子:

const wxRequest = (url, data = {}, method = 'GET') => {
    new Promise((resolve, reject) => {
        wx.request({
            url,
            data,
            method,
            header: {},
            success (res) => {
                const code = res.statusCode
                if (code !== 200) {
                    reject({ error: 'request fail', code })
                    return
                }
                resolve(res.data)
            },
            fail (res) {
                reject({ error: 'request fail' })
            }
        })
    })
}

当然, 上面代码我们也可以使其具有通用性, 能够Promise化更多类似的接口:

const promisify = fn => args => {
    new Promise((resolve, reject) => {
        args = {
            ...args,
            success: (res) => {
                return resolve(res)
            },
            fail: (res) => {
                return reject(res)
            }
        }
    })
}

将Promise化的代码封装为Promisify之后, 我们便可以像如下代码所示的那样使用它

const wxRequest = promisify(wx.request)

Promise初见雏形

通过上面的例子, 我们知道Promise其实就是一个构造函数, 该构造函数很简单, 它只有一个参数, 按照Promise/A+规范的命名, 我们把Promise构造函数的参数叫作executor, 这个参数是一个具有resolve、reject两个方法作为参数.

到这里我们开始实现Promise的第一步, 先简单实现一个构造函数, 代码如下:

function Promise (executor) {}

我们再回头观察下前面的代码, 可以发现Promise构造函数返回一个Promise对象实例, 这个返回的Promise对象具有一个then方法. 在then方法中, 调用者可以定义两个参数: onfulfilled和onrejected, 它们都是函数类型的参数. 其中onfulfilled通过参数可以获取Promise对象经过resolve处理后的值, onrejected可以获取Promise对象经过reject处理后的值. 接下来我们继续实现Promise. 在已有Promise构造函数的基础上加上then原型方法.

function Promise (executor) {}
Promise.prototype.then = (onfulfilled, onrejected) => {}

从onfulfilled和onrejected的参数中, 我们可以知道, 在实现Promise时, 应该有两个变量, 分别存储经过resolve处理后的值, 以及经过reject处理后的值, 而因为Promise状态的唯一性, 不可能同时出现经过resolve处理后的值和经过reject处理后的值, 因此可以用一个变量来存储, 代码如下:

function Promise (executor) {
    this.status = 'pending'
    this.value = null
    this.reason = null
    const resolve = value => {
        this.value = value
    }
    const reject = reason => {
        this.reason = reason
    }
    executor(resolve, reject)
}
Promise.prototype.then = (onfulfilled, onrejected) => {
    onfulfilled(this.value)
    onrejected(this.reason)
}

Promise实现状态完善

我们先来看下一段代码, 可以判断下它的输出:

let promise = new Promise((resolve, reject) => {
    resolve('data')
    reject('error')
})
promise.then(data => {
    console.log(data)
}, error => {
    console.log(error)
})

以上代码只会输出data. 我们知道Promise实例的状态只能从pending变为fulfilled, 或者从pending变为rejected. 状态一旦变更完毕, 就不可再次变化或逆转.显然我们的代码实现无法满足这一特性, 这里我们来继续完善下:

function Promise (executor) {
    this.status = 'pending'
    this.value = null
    this.reason = null
    const resolve = value => {
        if (this.status === 'pending') {
            this.value = value
            this.status = 'fulfilled'
        }
    }
    const reject = reason => {
        this.reason = reason
        if (this.status === 'pending') {
            this.reason = reason
            this.status = 'rejected'
        }
    }
    executor(resolve, reject)
}
Promise.prototype.then = (onfulfilled, onrejected) => {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
    onrejected = typeof onfulfilled === 'function' ? onfulfilled : error => {
        throw(error)
    }
    if (this.status === 'fulfilled') {
        onfulfilled(this.value)
    }
    if (this.status === 'rejected') {
        onrejected(this.reason)
    }
}

这样一来, 我们的实现更加完善了, 刚才的代码也可以顺利执行. 但是我们要知道Promise是用来解决异步问题的, 而我们的代码全部都是同步执行的, Promise的核心逻辑还没有开始实现.

Promise异步实现完善

先看一段代码:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('data')
    }, 2000)
})
promise.then(data => {
    console.log(data)
})

正常来说, 上述代码会在2s后输出data, 但按照我们的代码来执行, 不会输出任何信息. 原因很简单, 因为我们的实现逻辑全是同步的. 示例代码中resolve在2s之后才会调用, 而我们的实现onfulfilled是同步执行的, 它在执行时status仍然为pending. 所以onfulfilled方法应该在resolve的时刻调用, 这样我们就在状态为pending时将onfulfilled方法存起来.

function Promise (executor) {
    this.status = 'pending'
    this.value = null
    this.reason = null
    this.onfulfilledFunc = null
    this.onrejectedFunc = null
    const resolve = value => {
        if (this.status === 'pending') {
            this.value = value
            this.status = 'fulfilled'
            this.onfulfilledFunc && this.onfulfilledFunc(value)
        }
    }
    const reject = reason => {
        this.reason = reason
        if (this.status === 'pending') {
            this.reason = reason
            this.status = 'rejected'
            this.onrejectedFunc && this.onrejectedFunc(reason)
        }
    }
    executor(resolve, reject)
}
Promise.prototype.then = (onfulfilled, onrejected) => {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
    onrejected = typeof onfulfilled === 'function' ? onfulfilled : error => {
        throw(error)
    }
    if (this.status === 'fulfilled') {
        onfulfilled(this.value)
    }
    if (this.status === 'rejected') {
        onrejected(this.reason)
    }
    if (this.status === 'pending') {
        this.onfulfilledFunc = onfulfilled
        this.onrejectedFunc = onrejected
    }
}

现在我们的实现代码支持异步执行了, 这里我们再来看一个例子:

let promise = new Promise((resolve, reject) => {
    resolve('data')
})
promise.then(data => {
    console.log(data)
})
console.log(1)

如果用我们的实现代码来执行, 会有两个问题:

  1. 先输出data,再输出1(按照Promise异步执行应该先输出1再输出data)

  2. onfulfilled可能会执行两次

因此需要将resolve和reject的执行放到任务队列中, 我们先放到setTimeout中:

const resolve = value => {
    if (value instanceof Promise) {
        return value.then(resolve, reject)
    }
    setTimeout(() => {
        if (this.status === 'pending') {
            this.value = value
            this.status = 'fulfilled'
            this.onfulfilledFunc && this.onfulfilledFunc(value)
        }
    })
}
const reject = reason => {
    setTimeout(() => {
        this.reason = reason
        if (this.status === 'pending') {
            this.reason = reason
            this.status = 'rejected'
            this.onrejectedFunc && this.onrejectedFunc(reason)
        }
    })
}
executor(resolve, reject)

到目前为止, 整个实现代码如下所示:

function Promise (executor) {
    this.status = 'pending'
    this.value = null
    this.reason = null
    this.onfulfilledFunc = null
    this.onrejectedFunc = null
    const resolve = value => {
        if (value instanceof Promise) {
            return value.then(resolve, reject)
        }
        setTimeout(() => {
            if (this.status === 'pending') {
                this.value = value
                this.status = 'fulfilled'
                this.onfulfilledFunc && this.onfulfilledFunc(value)
            }
        })
    }
    const reject = reason => {
        setTimeout(() => {
            this.reason = reason
            if (this.status === 'pending') {
                this.reason = reason
                this.status = 'rejected'
                this.onrejectedFunc && this.onrejectedFunc(reason)
            }
        })
    }
    executor(resolve, reject)
}
Promise.prototype.then = (onfulfilled, onrejected) => {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
    onrejected = typeof onfulfilled === 'function' ? onfulfilled : error => {
        throw(error)
    }
    if (this.status === 'fulfilled') {
        onfulfilled(this.value)
    }
    if (this.status === 'rejected') {
        onrejected(this.reason)
    }
    if (this.status === 'pending') {
        this.onfulfilledFunc = onfulfilled
        this.onrejectedFunc = onrejected
    }
}

Promise细节完善

接下来我们来完善下实现细节. 比如, 在Promise实例状态变更之前添加多个then方法:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('data')
    }, 2000)
})
promise.then(data => {
    console.log(`1: ${data}`)
})
promise.then(data => {
    console.log(`2: ${data}`)
})

上面代码正常输出应该是1: data, 2: data. 而我们的代码只会输出2: data, 因为第二个then方法中的onfulfilledFunc会覆盖第一个then方法中的onfulfilledFunc. 解决办法就是将所有then方法中的onfulfilledFunc存到一个数组中, 在当前Promise被决议时依次执行数组内的方法即可. 对于onrejectedFunc同理, 改动后的实现代码如下:

function Promise (executor) {
    this.status = 'pending'
    this.value = null
    this.reason = null
    this.onfulfilledFuncArr = []
    this.onrejectedFuncArr = []
    const resolve = value => {
        if (value instanceof Promise) {
            return value.then(resolve, reject)
        }
        setTimeout(() => {
            if (this.status === 'pending') {
                this.value = value
                this.status = 'fulfilled'
                this.onfulfilledFuncArr.forEach(func => {
                    func(this.value)
                })
            }
        })
    }
    const reject = reason => {
        setTimeout(() => {
            this.reason = reason
            if (this.status === 'pending') {
                this.reason = reason
                this.status = 'rejected'
                this.onrejectedFuncArr.forEach(func => {
                    func(this.reason)
                })
            }
        })
    }
    executor(resolve, reject)
}
Promise.prototype.then = (onfulfilled, onrejected) => {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
    onrejected = typeof onfulfilled === 'function' ? onfulfilled : error => {
        throw(error)
    }
    if (this.status === 'fulfilled') {
        onfulfilled(this.value)
    }
    if (this.status === 'rejected') {
        onrejected(this.reason)
    }
    if (this.status === 'pending') {
        this.onfulfilledFuncArr.push(onfulfilled)
        this.onrejectedFuncArr.push(onrejected)
    }
}

另外一个需要完善的细节是, 在构造函数执行中如果出错, 将会自动触发Promise实例状态变为rejected, 因此我们用try...catch块对executor进行包裹:

try {
    executor(resolve, reject)
} catch (err) {
    reject(err)
}

Promise then的链式调用

我们先来看两段代码:

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('jacky')
    }, 2000)
})
promise.then(data => {
    console.log(data)
    return `${data} next then`
}).then(data => {
    console.log(data)
})
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('jacky')
    }, 2000)
})
promise.then(data => {
    console.log(data)
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`${data} next then`)
        }, 4000)
    })
}).then(data => {
    console.log(data)
})

分别执行上述两段代码, 我们可以看到第一段代码会在2s后输出jacky, 紧接着输出jacky next then, 第二段代码会在2s后输出jacky, 紧接着再过4s输出jacky next then. 由此可见, 一个Promise实例then方法的onfulfilled函数和onrejected函数是支持再次返回一个Promise实例的, 也支持返回一个非Promise实例的普通值. 并且返回的这个Promise实例或这个非Promise实例的普通值将会传给下一个then方法的onfulfilled函数或onrejected函数. 如此then方法就支持链式调用了.

在前面实现的then方法中, 我们可以创建一个新的Promise实例promise2, 并最终将这个promise2返回, 实现代码如下:

Promise.prototype.then = (onfulfilled, onrejected) => {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
    onrejected = typeof onfulfilled === 'function' ? onfulfilled : error => {
        throw(error)
    }
    let promise2
    if (this.status === 'fulfilled') {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let result = onfulfilled(this.value)
                    resolve(result)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
    if (this.status === 'rejected') {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let result = onrejected(this.value)
                    resolve(result)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
    if (this.status === 'pending') {
        return promise2 = new Promise((resolve, reject) => {
            this.onfulfilledFuncArr.push(() => {
                try {
                    let result = onfulfilled(this.value)
                    resolve(result)
                } catch (e) {
                    reject(e)
                }
            })
            this.onrejectedFuncArr.push(() => {
                try {
                    let result = onrejected(this.value)
                    resolve(result)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}

至此Promise实现的完整代码如下:

function Promise (executor) {
    this.status = 'pending'
    this.value = null
    this.reason = null
    this.onfulfilledFuncArr = []
    this.onrejectedFuncArr = []
    const resolve = value => {
        if (value instanceof Promise) {
            return value.then(resolve, reject)
        }
        setTimeout(() => {
            if (this.status === 'pending') {
                this.value = value
                this.status = 'fulfilled'
                this.onfulfilledFuncArr.forEach(func => {
                    func(this.value)
                })
            }
        })
    }
    const reject = reason => {
        setTimeout(() => {
            this.reason = reason
            if (this.status === 'pending') {
                this.reason = reason
                this.status = 'rejected'
                this.onrejectedFuncArr.forEach(func => {
                    func(this.reason)
                })
            }
        })
    }
    executor(resolve, reject)
}
Promise.prototype.then = (onfulfilled, onrejected) => {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
    onrejected = typeof onfulfilled === 'function' ? onfulfilled : error => {
        throw(error)
    }
    let promise2
    if (this.status === 'fulfilled') {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let result = onfulfilled(this.value)
                    resolve(result)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
    if (this.status === 'rejected') {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let result = onrejected(this.reason)
                    resolve(result)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
    if (this.status === 'pending') {
        return promise2 = new Promise((resolve, reject) => {
            this.onfulfilledFuncArr.push(() => {
                try {
                    let result = onfulfilled(this.value)
                    resolve(result)
                } catch (e) {
                    reject(e)
                }
            })
            this.onrejectedFuncArr.push(() => {
                try {
                    let result = onrejected(this.reason)
                    resolve(result)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}

Promise静态方法和其他方法实现

在这一部分, 我们将实现以下方法:

  • Promise.prototype.catch
  • Promise.resolve
  • Promise.reject
  • Promise.all
  • Promise.race
Promise.prototype.catch

Promise.prototype.catch可以用来进行异常捕获, 我们知道then方法的第二个参数也是进行异常捕获的, 通过这个特性我们可以简单实现catch方法.

Promise.prototype.catch = catchFunc => {
    return this.then(null, catchFunc)
}
Promise.resolve实现

Promise.resolve(value)方法返回一个以给定值解析后的Promise实例对象. 先来看一个示例:

Promise.resolve(‘data').then(data => {
    console.log('data')
})
console.log(1)

执行以上代码可以看到输出结果为先输出1, 再输出data. 其实实现Promise.resolve(value)也很简单. 代码如下:

Promise.resolve = value => {
    return new Promise((resolve, reject) => {
        resolve(value)
    })
}
Promise.reject(value)实现

Promise.reject(value)实现与Promise.resolve(value)类似, 代码如下:

Promise.reject = value => {
    return new Promise((resolve, reject) => {
        reject(value)
    })
}
Promise.all实现

Promise.all方法返回一个Promise实例, 如果参数内的所有Promise实例都resolved完成回调, 如果参数中的Promise实例有一个rejectd, 则此实例回调rejectd, 失败原因是第一个Promise实例失败的原因.

Promise.all = promiseArr => {
    if (!Array.isArray(promiseArray)) {
        throw new TypeError('The arguments should be an array!')
    }
    return new Promise((resolve, reject) => {
        try {
            const res = []
            const len = promiseArr.length
            for (let i = 0; i < len ;i++) {
                promiseArr[i].then(data => {
                    res.push(data)
                    if (res.length === len) {
                        resolve(res)
                    }
                }, reject)
            }
        } catch (e) {
            reject(e)
        }
    })
}
Promise.race实现

Promise.race与Promise.all类似, 代码如下:

Promise.race = promiseArr => {
    if (!Array.isArray(promiseArray)) {
        throw new TypeError('The arguments should be an array!')
    }
    return new Promise((resolve, reject) => {
        try {
            const len = promiseArr.length
            for (let i = 0; i < len ;i++) {
                promiseArr[i].then(resolve, reject)
            }
        } catch (e) {
            reject(e)
        }
    })
}