手写Promise
一、创建基础框架
/** 这是Promise写法 */
let promise = new Promise((resolve, reject) => {
resolve('返回数据')
reject('错误信息') // Promise中,仅会执行resolve/reject中一个
})
promise.then(
res => { console.log(res) }, // 仅有这里输出 '返回数据'
err => { console.log(err) }
)
模仿到Promise的例子,我们来创建自己手写的Promise。
class Commitment {
// 三个基础状态 并且PENDING可变一次到FULFILLED/REJECTED,不可再次改变状态
static PENDING = 'pending'
static FULFILLED = 'fulfilled'
static REJECTED = 'rejected'
constructor(func) {
// 实例化后,调用时status初始值为PENDING
this.status = Commitment.PENDING
this.result = null;
// 因为实例化后,在外部调用的resolve/reject的this指向并不是Commitment构造函数,
// 所以需要在调用resolve/reject时用.bind把this指向修改正确
func(this.resolve.bind(this), this.reject.bind(this))
}
resolve(result) {
if (this.status !== Commitment.PENDING) return // 仅可在PENDING状态下执行
// 把状态修改成对应值
this.status = Commitment.FULFILLED
// 保存结果
this.result = result
}
reject(result) {
if (this.status !== Commitment.PENDING) return // 仅可在PENDING状态下执行
// 把状态修改成对应值
this.status = Commitment.REJECTED
// 保存结果
this.result = result
}
}
let commitment = new Commitment((resolve, reject) => {
resolve('返回数据')
})
在上面的代码中,已经实现了一个基础的、同步的Promise使用示例,接下来我们进行.then()调用
二、实现.then方法
我们知道 promise.then(func1, func2) 中可以接收两个参数作为回调,一个接收成功resolve回调,第二个接收失败reject回调。
class Commitment {
...
then(onFULFILLED, onREJECTED) {
if (this.status === Commitment.FULFILLED) {
onFULFILLED(this.result)
}
if (this.status === Commitment.REJECTED) {
onREJECTED(this.result)
}
}
}
let commitment = new Commitment((resolve, reject) => {
resolve('返回数据')
})
commitment.then(
res => console.log(res), // 拿到 '返回数据'
err => console.log(err)
)
看起来我们已经实现了Promise的部分逻辑,这里会遇到两个问题。
1. 当resolve阶段发生错误信息时
在原生Promise中,当调用回调函数的函数块内发生错误,Promise.then可以正确捕获到错误,并在err中返回错误信息
let promise = new Promise((resolve, reject) => {
throw new Error('错误信息')
})
promise.then(
res => console.log(res),
err => console.log(err.message) // '错误信息'
)
目前我们写的代码中,还没有这一步的逻辑处理,如果按照以上方法调用的话,会直接在throw那行直接抛出异常,后续的 .then 并不会捕获到错误信息。那我们就需要在最初执行函数的时候,进行try catch捕获异常,如果捕获到异常情况,则直接调用this.reject(error)并把错误信息传进去。这样就实现了跟原生Promise的效果一样了。
class Commitment {
...
constructor(func) {
try {
func(this.resolve.bind(this), this.reject.bind(this))
} catch (error) {
// 这里不需要再次用bind去绑定this,因为这里是直接去调用构造函数的方法
this.reject(error)
}
}
}
2. 当.then传入的不是函数时
在Promise的 .then 是可以传不是函数的代码作为参数,传入其他值,可以得到正常的调用结果。
let promise = new Promise((resolve, reject) => {
resolve('返回数据')
})
promise.then(
undefined,
err => { console.log(err) }
)
// 以上代码实际输出的还是调用成功的逻辑,具体返回内容为Promise对象
// Promise {<fulfilled>: '返回数据'}
那我们手写的Commitment中,.then 方法传入undefined的话,会出现什么结果,没错onFULFILLED is not a function,会提示这个玩意儿不是个方法,所以在调用前,我们需要对 .then 中的传入值做一个简单的验证。
class Commitment {
...
then(onFULFILLED, onREJECTED) {
onFULFILLED = typeof onFULFILLED === 'function' ? onFULFILLED : () => {}
onREJECTED = typeof onREJECTED === 'function' ? onREJECTED : () => {}
...
}
}
三、实现异步操作
目前Commitment的整体流程还是同步的,并没有达到Promise的异步效果
console.log('第一步')
let commitment = new Commitment((resolve, reject) => {
console.log('第二步')
resolve('返回数据')
})
commitment.then(
res => console.log(res),
err => console.log(err)
)
console.log('第三步')
// 第一步
// 第二步
// 返回数据
// 第三步
那如果是原生的Promise是什么效果呢?
console.log('第一步')
let promise = new Promise((resolve, reject) => {
console.log('第二步')
resolve('返回数据')
})
promise.then(
res => console.log(res),
err => console.log(err)
)
console.log('第三步')
// 第一步
// 第二步
// 第三步
// 返回数据
这时候我们发现,原生的Promise中,调用resolve方法后的回调函数是异步执行的,那我们就直接简单粗暴的给Commitment中的 .then 里面,添加上setTimeout用来实现异步。
class Commitment {
...
then(onFULFILLED, onREJECTED) {
...
setTimeout(()=> {
if (this.status === Commitment.FULFILLED) {
onFULFILLED(this.result)
}
if (this.status === Commitment.REJECTED) {
onREJECTED(this.result)
}
})
}
}
这时候再按上面的调用方式去执行,会发现console.log打印值的顺序跟Promise一样了。
四、 链式调用
Promise是可以支持链式调用的,多个promise.then().then()的执行,但我们目前的代码还是不能实现链式调用,那么接下来我们就来实现这个功能。
promise.then().then()其实就是让前一个then方法再返回一个新的Promise,这个新的Promise就有then方法了,这样就实现了链式调用。
class Commitment {
...
then(onFULFILLED, onREJECTED) {
// 直接包裹手写的Promise肯定不行,
// 还需要在下方调用onFULFILLED/onREJECTED时改为传给resolve/reject
return new Commitment((resolve, reject) => {
...
if (this.status === Commitment.FULFILLED) {
resolve(onFULFILLED(this.result))
}
if (this.status === Commitment.REJECTED) {
reject(onREJECTED(this.result))
}
...
})
}
}
let commitment = new Commitment((resolve, reject) => {
resolve('返回数据')
})
commitment
.then(
res => {
console.log(res)
return '1'
},
err => console.error(err)
)
.then(
res => {
console.log(res)
return '2'
},
err => console.error(err)
)
.then(res => console.log(res))
// 返回数据
// '1'
// '2'
五、 结语
现在满大街都是手写Promise的技术文章,自己跟着流程走一遍还是会熟悉很多,终于理解了Promise的一点点知识,后续会继续学习。
六、 源码
class Commitment {
static PENDING = '待定'
static FULFILLED = '成功'
static REJECTED = '拒绝'
constructor(func) {
this.status = Commitment.PENDING
this.result = null
try {
func(this.resolve.bind(this), this.reject.bind(this))
} catch (error) {
this.reject(error)
}
}
resolve(result) {
if (this.status !== Commitment.PENDING) return
this.status = Commitment.FULFILLED
this.result = result
}
reject(result) {
if (this.status !== Commitment.PENDING) return
this.status = Commitment.REJECTED
this.result = result
}
then(onFULFILLED, onREJECTED) {
return new Commitment((resolve, reject) => {
// 如果使用默认参数 会被传入进来的值覆盖掉,主要目的是为了验证传入进来的参数是否是函数
onFULFILLED = typeof onFULFILLED === 'function' ? onFULFILLED : () => {}
onREJECTED = typeof onREJECTED === 'function' ? onREJECTED : () => {}
setTimeout(() => {
if (this.status === Commitment.FULFILLED) {
resolve(onFULFILLED(this.result))
}
if (this.status === Commitment.REJECTED) {
reject(onREJECTED(this.result))
}
})
})
}
}
let commitment = new Commitment((resolve, reject) => {
resolve('返回数据')
})
commitment
.then(
res => {
console.log(res)
return '1'
},
err => console.error(err)
)
.then(
res => {
console.log(res)
return '2'
},
err => console.error(err)
)
.then(res => console.log(res))