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)
如果用我们的实现代码来执行, 会有两个问题:
-
先输出data,再输出1(按照Promise异步执行应该先输出1再输出data)
-
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)
}
})
}