最近查看Promise的实现这种文章,发现网上的教程都经不起推敲,一到then的链式调用云里雾里,所以我决定自己手写一下,再会过头发现 网上的将的Promise案例是错误的逻辑,所以大家看着看着就不懂了
但你认真看这篇手写Promise,你就会明白的Promise怎么实现的then链式调用的。
这里我不会进行对比的,大家看我这篇应该是能顺懂的,然后你再看其他的Promise教程,你就会发现他们的问题啦。
使用Promise
先来看看Promise 场景的两种使用情况:
/* 模拟一个简单的异步行为 */
function myAsyncFunction() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('123'), 1000);
});
}
// 用法1:
myAsyncFunction().then(res => {
console.log(res) // after 1000ms : 输出 123
}, (err) => {})
// 用法2:
myAsyncFunction().then(res => {
console.log('res:', res) // after 1000ms: 输出 123
return res + '_123'
}, (err) => {}).then(res => {
console.log('res:', res) // res: 123_123
return res + '_123'
}, (err) => {}).then(res => {
console.log('res:', res) // res: 123_123_123
}, (err) => {})
// 用法3:
let p = myAsyncFunction()
p.then(res => {
console.log(res) // 输出 res:123
return 456
}, (err) => {})
p.then(res => {
console.log(res) // 输出 res:123
}, (err) => {})
用法1是正常的使用,用法2叫链式调用,用法3是对同一个实例进行多次调用
看以上Prmise的使用我们大致有个框架了,下面来搭建一下基本框架。
简单版Promise
1. 搭建Promise框架
Promise 是一个类,它接收一个函数作为参数,会传递給该函数两个默认参数,来通知执行then
方法去执行成功或者失败的函数。
// new Promise((resolve, reject) => { resolve('123') });
class MyPromise {
// executor 为用户传递进来的函数
constructor(executor) {
executor(this.resolve(), this.reject())
}
resolve(data) {}
reject(reason) {}
then(onFulfilled, onRejected) {}
}
2. 设置Promise状态
从Promise的官方规范可知Promise有三种状态:pending、fulfilled、rejected
resolve(data)
函数代表成功,会将状态为fulfilled
reject(value)
函数代表失败,会将状态为rejected
另外,两个的函数执行会触发then
函数的执行,其中的data
、value
会传递给
then(resolveFunc, rejectFunc)
这两个回调函数当参数。
将上面的思路应用在上一步的框架上:
class MyPromise {
static pending = "pending";
static fulfilled = "fulfilled";
static rejected = "rejected";
constructor(executor) {
this.status = MyPromise.pending; // 初始化状态为 pending
this.data = undefined; // 操作成功时 存放成功数据
this.reason = undefined; // 操作失败时 存放失败的原因
this.resolveFunc = () => {}; // 操作成功时 存放成功的回调函数
this.rejectFunc = () => {}; // 操作失败时 存放失败的回调函数
// 应为resolve函数是在executor中调用 ,也就是默认resolve函数的this指向的是executor函数
// 所以要修改resolve的this指向为当前的MyPromise
executor(this.resolve.bind(this), this.reject.bind(this))
}
resolve(data) {
this.status = MyPromise.fulfilled; // 更改状态
this.data = data; // 存放成功的数据
// 执行操作成功时的回调函数
this.resolveFunc(this.data);
}
reject(reason) {
this.status = MyPromise.rejected; // 更改状态
this.reason = reason; // 存放失败的原因
// 执行操作失败时的回调函数
this.rejectFunc(this.reason);
}
then(onFulfilledFunc, onRejectedFunc) {
this.resolveFunc = onFulfilledFunc; // 存放操作成功时的回调函数
this.rejectFunc = onRejectedFunc; // 存放操作失败时的回调函数
}
}
这时MyPromis已经可以简单使用起来了,测试一下
function myAsyncFunction(isFulfilled = true) {
// isFulfilled 控制成功、失败
return new MyPromise((resolve, reject) => {
setTimeout(() => {
if (isFulfilled) {
resolve(123);
} else {
reject("我是失败的原因");
}
}, 1000);
});
}
myAsyncFunction().then(res => {
console.log(res) // after 1000ms : 输出 123
}, err => {})
myAsyncFunction(false).then(res => {}, err => {
console.log(err) // after 1000ms : 输出 我是失败的原因
})
3. 创建then链式调用
从上面的输出结果来看,常规使用是没什么问题的。接下来就是像下面这样用的链式调用我们是不支持的。按照下面的情况我们进一步完善。
// 用法2:
myAsyncFunction().then(res => {
console.log('res:', res)
return res + '_123'
}, (err) => {}).then(res => {
console.log('res:', res)
return res + '_123'
}, (err) => {}).then(res => {
console.log('res:', res)
}, (err) => {})
在我想既然能链式调用,我起初想的是在then中返回当前的this,就能继续往下调用了吧,尝试一下
先在 then()
中 添加 return this
// 用法2:
myAsyncFunction().then(res => {
console.log('res:', res) // after 1000ms: 输出 123
return res + '_123'
}, (err) => {}).then(res => {
console.log('res:', res) // 没有执行
return res + '_123'
}, (err) => {}).then(res => {
console.log('res:', res) // 没有执行
}, (err) => {})
在运行过程中是没有报错,但从第二个then
开始就没有执行传递的回调函数,因为这两个函数是通过resolve()
和reject()
执行的,但到第二个then
没有去执行resolve()
和reject()
。也就不会执行then传递的回调函数。
所以返回this这条路不行,还可以返回一个新的Promise,这样就可以继续调用then了。
下面将过程抽象一下:
class MyPromise{
...
resolve(data) {
this.status = MyPromise.fulfilled; // 更改状态
this.data = data; // 存放成功的数据
// 执行操作成功时的回调函数
this.resolveFunc(this.data);
}
then(onFulfilledFunc, onRejectedFunc){
// this.resolveFunc = onFulfilledFunc; // 存放操作成功时的回调函数
// this.rejectFunc = onRejectedFunc; // 存放操作失败时的回调函数
let that = this
// 注意:下面的this都是指向的是调用者
// 也就是调用者成功才会促使p成功
let p = new MyPromise( (resolve, reject) => {
that.resolveFunc = () => {
let data = onFulfilledFunc(this.data);
resolve(data); // 将值传递下去
}
that.rejectFunc = () => {
onRejectedFunc(this.reason);
// 失败就不用继续传递了
}
})
return p // 返回新Promise实例
}
}
// 使用
let P1 = new MyPromise(...);
P1.then(res => res + '_123') // 这个then的调用者是P1
.then(res => res + '_123') // 这个then的调用者是P1 then产生的P2
.then(res => res + '_123') // 这个then的调用者是P2 then产生的P3
总结:P1的成功 才会调用P2的成功 才会调用P3的成功,这情况不就是链式调用了么。
测试情况如下:
myAsyncFunction().then(res => {
console.log('res:', res)
return res + '_123'
}, (err) => {}).then(res => {
console.log('res:', res)
return res + '_123'
}, (err) => {}).then(res => {
console.log('res:', res)
}, (err) => {})
// after 1000ms 输出
res: 123
res: 123_123
res: 123_123_123
从结果上说明,下个then能拿到上一个的值,并将值传递回去,这里链式调用通了,但如果值是一个新的Promise呢,对于这种情况先想一下, 首先需要在接收值得时候判断一下是否为Promise的实例,然后为该实例添加then函数,当实例成功或失败的时候还是走原来的逻辑。
3. 完善then链式调用
then(onFulfilledFunc, onRejectedFunc){
// 注意:下面的this都是指向的是调用者
// 也就是调用者成功才会促使p成功
let p = new MyPromise( (resolve, reject) => {
this.resolveFunc = () => {
let data = onFulfilledFunc(this.data);
// 判断返回值是否为新的Promise
if (data instanceof MyPromise) {
// 此时 data 为 promise
// 为实例设置成功、失败的回调函数
data.then((y) => resolve(y), (err) => reject(err));
// 注意 reslove 、reject都是p的
// 这样在调用data.then之后 就间接通知p成功、失败并把值传给p.then
}else{
resolve(data); // 将值传递下去
}
}
this.rejectFunc = () => {
onRejectedFunc(this.reason);
// 失败就不用继续传递了
}
})
return p // 返回新Promise实例
// 调用示例: p.then().then().then()
}
这样就处理完对于返回MyPromise的情况,测试一下:
myAsyncFunction().then(res => {
console.log('res:', res)
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(456)
}, 1000);
});
}, (err) => {}).then(res => {
console.log('res:', res)
return res + '_123'
}, (err) => {}).then(res => {
console.log('res:', res)
}, (err) => {})
// after 1000ms: 输出
res: 123
// after 1000ms: 输出
res: 456
res: 456_123
发现是成功的,说明链式调用我们完成了。
但我发现还有用法3这种情况:同一个实例多次调用then
// 用法3:
function myAsyncFunction() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('123'), 1000);
});
}
let p = myAsyncFunction()
p.then(res => {
console.log(res) // 输出 res:123
return 456 // 注意它不会影响p
}, (err) => {})
p.then(res => {
console.log(res) // 输出 res:123
}, (err) => {})
发现了么,第一个p then 的 返回 456 不会修改下一次调用p的结果,这样也合理他们的调用者都是p,所以在then中返回值是没有用的,按这种要求我们改起来还是很简单的,直接看代码。
class MyPromise {
constructor(executor) {
....
// this.resolveFunc = () => {}; // 操作成功时 存放成功的回调函数
// this.rejectFunc = () => {}; // 操作失败时 存放失败的回调函数
// 用数组存放同一个实例多次调用then的回调函数
// 注意哦:不是为了then的链式调用才用数组,是为了同一个实例多次调用
this.resolveFuncArr = []; // 操作成功时 存放成功的回调函数
this.rejectFuncArr = []; // 操作失败时 存放失败的回调函数
}
resolve(data) {
...
// this.resolveFunc(this.data);
this.resolveFuncArr.forEach( resolveFunc=> resolveFunc(this.data) )
}
reject(reason) {
...
// this.rejectFunc(this.reason);
this.rejectFuncArr.forEach( rejectFunc=> rejectFunc(this.reason) )
}
then(onFulfilledFunc, onRejectedFunc){
let p = new MyPromise( (resolve, reject) => {
// this.resolveFunc = () => {...}
this.resolveFuncArr.push(() => {
let data = onFulfilledFunc(this.data);
// 判断返回值是否为新的Promise
if (data instanceof MyPromise) {
// 此时 data 为 promise
// 为实例设置成功、失败的回调函数
data.then((y) => resolve(y), (err) => reject(err));
// 注意 reslove 、reject都是p的
// 这样在调用data.then之后 就间接通知p成功、失败并把值传给p.then
}else{
resolve(data); // 将值传递下去
}
})
// this.rejectFunc = () => {...}
this.rejectFuncArr.push(() => {
onRejectedFunc(this.reason);
// 失败就不用继续传递了
})
})
return p // 返回新Promise实例
}
}
以上就是完成了对同一个实例多次调用then的封装,其实就是换了一种存储方式,测试一下
let p = myAsyncFunction()
p.then(res => {
console.log(res) // 输出 res:123
return 456
}, (err) => {})
p.then(res => {
console.log(res) // 输出 res:123
}, (err) => {})
// after 1000ms 输出
res:123
res:123
以上就完成了Promise的总体框架。
完整版Promise
看一下总体代码:
class MyPromise {
static pending = "pending";
static fulfilled = "fulfilled";
static rejected = "rejected";
constructor(executor) {
this.status = MyPromise.pending; // 初始化状态为 pending
this.data = undefined; // 操作成功时 存放成功数据
this.reason = undefined; // 操作失败时 存放失败的原因
// 用数组存放同一个实例多次调用then的回调函数
// 注意哦:不是为了then的链式调用才用数组,是为了同一个实例多次调用
// 即不是为了 p.then().then().then() 而是为了: p.then() p.then() p.then()
this.resolveFuncArr = []; // 操作成功时 存放成功的回调函数
this.rejectFuncArr = []; // 操作失败时 存放失败的回调函数
// 应为resolve函数是在executor中调用 ,也就是默认resolve函数的this指向的是executor函数
// 所以要修改resolve的this指向为当前的MyPromise
executor(this.resolve.bind(this), this.reject.bind(this))
}
resolve(data) {
this.status = MyPromise.fulfilled; // 更改状态
this.data = data; // 存放成功的数据
// 执行操作成功时的回调函数
this.resolveFuncArr.forEach( resolveFunc=> resolveFunc(this.data) )
}
reject(reason) {
this.status = MyPromise.rejected; // 更改状态
this.reason = reason; // 存放失败的原因
// 执行操作失败时的回调函数
this.rejectFuncArr.forEach( rejectFunc=> rejectFunc(this.reason) )
}
then(onFulfilledFunc, onRejectedFunc){
// 注意:下面的this都是指向的是上一个then
// 也就是调用者成功才会促使p成功
let p = new MyPromise( (resolve, reject) => {
this.resolveFuncArr.push(() => {
let data = onFulfilledFunc(this.data);
// 判断返回值是否为新的Promise
if (data instanceof MyPromise) {
// 此时 data 为 promise
// 为实例设置成功、失败的回调函数
data.then((y) => resolve(y), (err) => reject(err));
// 注意 reslove 、reject都是p的
// 这样在调用data.then之后 就间接通知p成功、失败并把值传给p.then
}else{
resolve(data); // 将值传递下去
}
})
this.rejectFuncArr.push(() => {
onRejectedFunc(this.reason);
// 失败就不用继续传递了
})
})
return p // 返回新Promise实例
}
}
至此,一个Promise框架就完成了,当然还有很多需要处理,比如循环调用问题,异常处理等等,但这些都是细节,也就是不用太费脑的事情,不过是在相应的地方加上判断或加上try{}catch{} 或者在进一步抽象出函数让代码可读性更高即可。
欢迎大家留言讨论