promise函数自定义封装详解(二)
promise函数自定义封装详解(二)
大家好,在上一篇文章当中我们基本完成了promise构造函数的声明和内部代码的实现,以及promise函数then方法的分析,编写与调用。但代码总体上来说尚待完善,因为我们只考虑到了同步任务下的then方法相关打印,那如果此时exector构造器函数里传的是一个setTimeout函数,而在这个异步任务里我们再去调用resolve或者reject方法,那此时去执行then方法,控制台会出现什么内容呢?
目标
这节课我们需要实现的目标如下:
- 实现异步任务的成功执行
- promise构造函数优化
- then方法返回值优化
setTimeout异步任务
发现问题
开头提到,之前我们都是在同步任务下调用then,此时我们在exector执行器函数内传入异步任务setTimeout,在这个任务内在调用resolve或reject看,来看效果
// 创建一个promise实例化对象p
let p = new Promise((resolve, reject) => {
// 传入异步任务定时器函数
setTimeout(() => {
// 调用reject方法
reject('失败')
}, 500)
})
// 调用promise的then方法
p.then(value => {
// 执行p状态成功时的回调
console.log(value);
}, reason => {
// 执行p状态失败时的回调
console.log(reason);
})
打印效果:
我们惊奇地发现,控制台没有打印任何结果,这是为什么呢?我们来分析一下这个现象。
问题分析
首先,executor执行器函数本身是个同步任务,但里面的setTimeout是个异步任务,需要我们等到500毫秒到了我们才能执行里面的reject函数。而我们下面的then方法在此时也是个同步任务,它不会等待setTimeout的执行结果出来之后再执行,而是在构造器函数执行完之后,它就立刻执行。
前面我们提到过,调用then方法时,我们到底要执行里面的参数为value的函数,还是参数为reason的函数,得看executor函数内到底是调用resolve还是调用reject,而此时我们的then方法已经立刻调用了,但是迟迟收不到上面定时器传下来的结果,所以then在极其短暂的时间内无法判断到底执行哪一条,所以就都没有执行,因此控制台上没有显示打印结果。
我们可以尝试着在then方法外面套一个时间为600毫秒的定时器,如图
打印效果:
这样一来,我的then是600毫秒时触发,而构造器函数内部的异步任务是500毫秒时触发,因此then完全可以接收到上面传下来的结果并执行相应内容。
新的思路
如果我们只局限于在then方法外面套定时器,可能会导致后面的代码可读性与可移植性降低,那我们又想实现相同的效果,该怎么办呢?此时我们需要在js文件中对异步任务的相关实现进行操作。
还记得我们之前在promise构造函数内声明的callbacks空数组吗?现在我们就需要用到它了。我们这里大概理清一下思路
- 首先我们利用if语句判断实例对象的状态此时是否为pending
- 如果为pending,我们再执行花括号内相应的代码
- 接着我们要向这个callbacks空数组内推入一个对象,这个对象内包含两个函数onResolved和onRejected
具体代码如下
// 在promise函数原型上挂载一个then方法
Promise.prototype.then = function (onResolved, onRejected) {
// 成功时的then方法的调用
if (this.PromiseState === 'fulfilled') {
onResolved(this.PromiseResult)
}
// 失败时的then方法的调用
if (this.PromiseState === 'rejected') {
onRejected(this.PromiseResult)
}
// 状态为pending时then方法的调用
if (this.PromiseState === 'pending') {
this.callbacks.push({
onResolved: onResolved,
onRejected: onRejected
})
}
}
此时我们已经成功写出了状态为pending时所需要执行的代码,注意此时if语句内的this指向的是实例对象。接着我们再去promise构造函数内的两个函数体进行一些完善步骤
// 成功时的函数
function resolve(data) {
// 设置实例成功时的状态和结果值
self.PromiseState = 'fulfilled'
self.PromiseResult = data
// 执行调用函数为resolve的异步任务
self.callbacks.forEach(item => {
item.onResolved(data)
})
}
// 失败时的函数
function reject(data) {
// 设置实例成功时的状态和结果值
self.PromiseState = 'rejected'
self.PromiseResult = data
// 执行调用函数为reject的异步任务
self.callbacks.forEach(item => {
item.onRejected(data)
})
}
可以看到我们分别在resolve和reject函数体内加入了执行异步时的代码,我们在这里利用forEach方法对callbacks内的元素进行遍历,如果此时里面有元素(对象),那就把data值传入这个函数,并执行这个函数。
注意 item.onRejected(…)/item.onResolved(…),这里实现了判断为异步任务时的函数执行,而括号()内的data参数是否参与函数体内的运算,得看script模块中执行then方法时我们具体要做什么,也就是说,data不是必然参与运算
这样一来,我们就基本实现了异步任务的相关操作,这里大家可以稍稍理清一下思路,思考一下如何编写,如何实现,如何执行。
优化promise构造函数
我们下面对promise构造函数内的代再进行一些完善,首先看代码
// 创建一个promise实例化对象
let p = new Promise((resolve, reject) => {
resolve('成功')
reject('失败')
})
// 调用promise的then方法
p.then(value => {
// 执行p状态成功时的回调
console.log(value);
}, reason => {
// 执行p状态失败时的回调
console.log(reason);
})
发现问题
可以看到我们在executor执行器函数内既调用了resolve方法,也调用了reject方法。按照JS内置的promise函数执行结果来看,如果里面不存在异步任务,我们只能执行第一个调用函数,第二个就不能执行,因为promise函数状态只能改变一次。但我们现在打印来看
分析问题
可以看到,控制台只执行了reject(‘失败’)的相应内容。为什么呢?原来是我们虽然首先调用了resolve函数,改变了p的状态和结果值,但是紧接着执行第二行reject函数,在这个过程中我们覆盖了前面resolve的实现结果,准确来说是改变了对象p内PromiseState和PromiseResult原来的指向。因此,后面then方法会判断此时p的状态为reject,所有只执行了参数为reason的函数,所以控制台上只显示了”失败”。
那我们我们如何只改变一次结果呢?很简单,只要在promise构造函数中的resolve函数或者reject函数里面判断上一次的实例p的状态是否已经改变,如果没改变,那就正常往下执行。如果已经改变过,那就直接return出去,不再执行下面的代码,因此代码如下:
代码优化
// 成功时的函数
function resolve(data) {
// 判断此时的p的状态是否已经改变
if (self.PromiseState !== 'pending') return
// 设置实例成功时的状态和结果值
self.PromiseState = 'fulfilled'
self.PromiseResult = data
// 执行调用函数为resolve的异步任务
self.callbacks.forEach(item => {
item.onResolved(data)
})
}
// 失败时的函数
function reject(data) {
// 判断此时的p的状态是否已经改变
if (self.PromiseState !== 'pending') return
// 设置实例成功时的状态和结果值
self.PromiseState = 'rejected'
self.PromiseResult = data
// 执行调用函数为reject的异步任务
self.callbacks.forEach(item => {
item.onRejected(data)
})
}
现在我们再来打印,如图
这样一来,我们就完成了promise构造函数内部的优化,也实现了相应的目标。
then方法返回值优化
发现问题
现在我们来声明一个变量来接收script模块里then方法的返回值并将其打印:
//设置变量res接收then方法的返回值
let res = p.then(value => {
// 执行p状态成功时的回调
console.log(value);
}, reason => {
// 执行p状态失败时的回调
console.log(reason);
})
//打印res
console.log(res);
此时控制台打印的结果为undefined,这是为何呢?
分析问题
原来我们在js文件中,对于then方法并没有设置任何返回值,而仅仅是执行了then方法相关调用,为了和js内置promise函数then方法的返回值也为promise保持一致,我们应该对then方法进行某些新优化,那具体是什么呢?
此时我们必要要让我们的then方法也得返回一个promise对象,那该怎么操作呢?很简单,我们让then方法里的整体再次返回一个新的promise,来看代码
代码优化
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
// 成功时的then方法的调用
if (this.PromiseState === 'fulfilled') {
onResolved(this.PromiseResult)
}
// 失败时的then方法的调用
if (this.PromiseState === 'rejected') {
onRejected(this.PromiseResult)
}
// 状态为pending时then方法的调用
if (this.PromiseState === 'pending') {
this.callbacks.push({
onResolved: onResolved,
onRejected: onRejected
})
}
})
}
此时我们再来打印结果,如图
这时候我们成功的让then方法执行的返回值为一个promise对象,可以看到这时候新对象的PromiseState和PromiseResult的值的都为默认值,这是因为新promise对象的exector执行器函数传参到上面的构造函数promise中,重新接受了默认的结果和状态。
抛错测试
我们在这里可以尝试一下在script模块里的then方法里抛错,看看我们之前在promise构造函数里利用try..catch..是否捕捉到了异常,并打印最终的结果
打印结果:
可以看到我们的抛错测试成功,这里注意一点,一般当我们检测到throw 抛错并且执行时,我们的catch就会捕获这个错误,throw后面的函数就不会执行了
总结
这节课我们成功实现了异步任务的执行,以及promise构造函数优化和then方法返回值的完善,在后续的文章当中我们将要持续优化then方法内部的代码结构,以及添加promise的几种内置API,今天先到这里,谢谢大家!
本节代码
html代码
// 创建一个promise实例化对象
let p = new Promise((resolve, reject) => {
// // setTimeout 异步执行
// setTimeout(() => {
// resolve('成功')
// })
resolve('成功')
// reject('失败')
})
// 调用promise的then方法
let res = p.then(value => {
// 执行p状态成功时的回调
console.log(value);
//抛错测试
//throw 'Error'
}, reason => {
// 执行p状态失败时的回调
console.log(reason);
})
console.log(res);
js文件代码
// 定义promise构造函数
function Promise(executor) {
// 设置默认状态和默认值
this.PromiseState = 'pending'
this.PromiseResult = null
// 设置一个空数组,异步时调用
this.callbacks = []
// this赋给self
const self = this
// 成功时的函数
function resolve(data) {
// 判断此时的p的状态是否已经改变
if (self.PromiseState !== 'pending') return
// 设置实例成功时的状态和结果值
self.PromiseState = 'fulfilled'
self.PromiseResult = data
// 执行调用函数为resolve的异步任务
self.callbacks.forEach(item => {
item.onResolved(data)
})
}
// 失败时的函数
function reject(data) {
// 判断此时的p的状态是否已经改变
if (self.PromiseState !== 'pending') return
// 设置实例成功时的状态和结果值
self.PromiseState = 'rejected'
self.PromiseResult = data
// 执行调用函数为reject的异步任务
self.callbacks.forEach(item => {
item.onRejected(data)
})
}
// 执行函数并捕捉可能存在的异常
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
// 在promise函数原型上挂载一个then方法
Promise.prototype.then = function (onResolved, onRejected) {
// 返回一个新的promise对象
return new Promise((resolve, reject) => {
// 成功时的then方法的调用
if (this.PromiseState === 'fulfilled') {
onResolved(this.PromiseResult)
}
// 失败时的then方法的调用
if (this.PromiseState === 'rejected') {
onRejected(this.PromiseResult)
}
// 状态为pending时then方法的调用
if (this.PromiseState === 'pending') {
this.callbacks.push({
onResolved: onResolved,
onRejected: onRejected
})
}
})
}