本文已参与「新人创作礼」活动,一起开启掘金创作之路
theme: jzman
promise函数自定义封装详解(四)
大家好,欢迎来到promise自定义封装的最终章!在这节课中,我们将完成以下目标
为promise构造函数挂载catch方法
为promise构造函数挂载all方法
为promise构造函数挂载race方法
异步打印的相关优化
promise构造函数catch的挂载
catch方法用来检测一个程序在执行过程中可能会发生的异常情况,并捕获其异常的错误。如果这个程序运行正常,则catch不会执行自己内部的代码,反之就会执行。在promise函数中,catch方法的返回值为一个promise函数
JS内置promise
我们利用JS内置的promise函数
// 欢迎来到 我的世界-
// 基本格式
let p = new Promise((resolve, reject) => {
// resolve()
reject('失败')
})
let res = p.then(value => {
// ...
}).catch(error => {
return 'err'
})
console.log(res);
打印结果:
可以看到我们js内置的promise函数成功执行了catch方法,并且打印了catch返回的promise对象。
自定义promise
再来看我们定义的promise函数执行结果打印:
打印结果说catch不是一个函数,因此我们需要在js文件中给promise的原型上挂载一个catch方法。
先来分析,首先这个catch接受的参数为script模块里catch传递过来的参数,在这里表现为onRejected函数。其次我们需要在catch方法内部返回一个promise对象,而这个promise函数其实就是调用的then方法,只不过then里的第一个参数我们给它设置成undefined,因为我们的catch方法捕获的是异常情况。
现在来看代码
// 挂载catch方法
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
代码执行流程
重点:
-
html里的script模块中我们对promise对象res调用catch方法,并将我们的形参为error的函数传到刚才定义好的catch方法当中
-
catch方法返回并执行一个then方法,在then方法内部我们设置的第一个形参为undefined(不参与运算,但必须占位!),第二个参数是我们真正要使用到的参数,也就是这个error => { return 'error'} 函数。
-
传递至then方法内部后,再次返回一个新的promise对象,因为我们的实例p此时的状态为rejected,因此我们执行common函数,参数就为"error"函数。
4.再来到common函数,此时type为error函数,执行type(self.PromiseResult)函数并返回一个结果值为 'err',所以现在的result是一个非promise类型的值。
5.判断为result为非promise对象,则执行promise构造函数当中的resolve方法,为这个catch方法的返回值绑定状态和结果值
现在再来看打印结果:
此时我们就成功执行了catch方法,返回了一个promise的对象,显示了相应的状态和结果值。
promise构造函数all方法
在讲解all方法具体挂载之前,我们先来简单回顾一下all方法
复习all方法
all: promise中all方法的参数接受一个数组,这个数组的成员要满足都为promise对象。如果所有的promise对象状态都为“fulfilled”,也就是成功,all方法返回一个状态也为成功的promise对象,此对象内部的结果值为此成功的数组。若有一个promise成员的状态为失败,那么all方法就会返回一个失败的promise对象,其状态为"rejected",其内部结果值为这个数组中失败的promise函数的结果值。
知道了all方法的功能之后,我们现在来分析一些如何成功的给promise函数挂载all方法。
构建思路
-
既然all方法接收的是一个包含promise对象的数组,所我们定义all方法时,形参位置就是一个数组。
-
我们要对这个数组里每个元素,也就是promise对象进行遍历,如果每个promise对象的都是一个执行结果为成功的对象,那么我们all方法也返回一个成功的状态,结果值为这个成功的数组。
-
否则,这个all方法返回的就是一个状态为失败的promise对象,结果值为数组中那个失败的结果值
4.如何让all方法的返回值中的结果值是一个全为成功的promise对象的数组呢?此时我们可以声明一个变量count,和一个空数组arr
5.当我们每检测到一个成功的promise对象时,让count自增,并把相应成功的promise对象放进arr数组中
6.如果count值与我们传进来的promise对象的个数相同,那么就把这个数组resolve出去,也就是给这个all方法返回的promise对象挂载相应结果。
all方法实现
来看代码
// 挂载all方法
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let count = 0;
let arr = []
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
count++;
arr[i] = v
if (count === promises.length) {
resolve(arr)
}
}, r => {
reject(r)
})
}
})
}
这样一来,我们就实现了all方法的相应书写,来看script中的代码和打印结果
// 欢迎来到 我的世界-
// 基本格式
let o = new Promise((resolve, reject) => {
// resolve()
resolve('成功')
})
let p = Promise.resolve('123')
let q = Promise.resolve(456)
let z = Promise.all([o, p, q])
console.log(z);
打印结果:
这样一来我们就完成了all方法的成功实现和打印,可以看到打印结果里新对象的状态为fulfiled,结果值为一个都是成功的promise对象的数组。
promise构造函数race方法
复习race方法
race: promise中race方法的参数接受一个数组,这个数组的成员要满足都为promise对象,race方法返回一个promise对象,这个对象的状态由数组中第一个状态改变的promise函数决定,且两个对象的状态保持一致,且其结果值为数组中第一个状态改变的promise函数的结果值。
构建思路
race的编写流程和all方法有异曲同工之妙,只不过race的返回结果的对象中的结果值和状态为其传入的数组中第一个改变状态的promise相同。因此针对于这种情况,我们把all的方法稍加修改就行:
race方法实现
let o = new Promise((resolve, reject) => {
setTimeout(() => {
reject('失败')
}, 500)
})
let p = Promise.reject('123')
let q = Promise.resolve(456)
let z = Promise.race([o, p, q])
console.log(z);
来看打印结果:
这样一来,我们就实现了race的方法
异步打印的相关优化
js内置promise函数执行情况
先来看js内置promise对象关于异步与同步的相关执行顺序,先上代码
// 基本格式
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行value')
console.log(1);
}, 100)
console.log(2);
})
p.then(value=>{
console.log(3);
},reason=>{
console.log(4);
})
console.log(5);
打印结果为
执行思路分析
先来分析为什么会有这样的打印顺序
-
首先,我们的promise函数本身是个同步任务,所以会执行里面的代码,遇到setTimeout函数,判断其为异步任务,所以把它先放进任务队列当中后面在执行。紧接着console.log(2)是个同步任务,所以直接执行打印 2 。
-
然后往下执行遇见then,此时then方法是个异步任务,把它放进异步队列,先不执行。
-
再次往下执行遇见console.log(2),判断为同步任务,此时打印5
-
此时所有的同步任务已经执行完毕,开始从异步队列中调取任务放入执行栈中执行有关任务。这时候setTimeout此事被触发,结果为resolve(...)以及打印了后面的1
-
then方法接收到了上面传来的resolve提示,于是执行自己内部的第一条语句 console.log(3),打印3
所以此时的打印结果依次为为2, 5 , 1 ,3
异步执行代码优化
我们需要在引入的js文件中对里的相关代码进行完善,一共有四处地方
第一处地方是then里的成功状态下执行的common,要使其变为异步函数,在外面给它加个定时器就行了,如代码:
// 成功时的then
if (this.PromiseState === 'fulfilled') {
setTimeout(() => {
common(onResolved)
})
}
}
第二处地方为then里的失败状态下执行的common,方法一样,上代码
// 失败时的then
if (this.PromiseState === 'rejected') {
setTimeout(() => {
common(onRejected);
})
}
第三处地方为promise构造函数内的状态为成功时异步任务执行,上代码
setTimeout(() => {
self.callbacks.forEach(item => {
item.onResolved(data)
})
})
第四处地方为promise构造函数内的状态为失败时异步任务执行,看这里
setTimeout(() => {
self.callbacks.forEach(item => {
item.onRejected(data)
})
})
此时我们就完成了js文件中所有异步代码的完善
总结
至此,我们完成了promise函数所有代码的编写及逻辑思路的连通。本次promise自定义封装用到了大量的关于js的知识,如闭包、原型链、this指向、同步异步任务...
谢幕
连续失眠的夜晚与清晨,以及路途中的疲惫劳顿,在停笔的这一刻都不足为道...本人的前端之旅或许正值扬帆,希望能尽快向编程界的各位朋友看齐,相信无论多大的风浪与烈阳都无法阻止我们对于代码的热爱以及对目标的执著...
这几篇看似篇幅不长的文章,里面可能会有诸多矛盾点和错别字等问题,如果各位朋友发现有错误,劳请在评论区或者私信我。如果这些文章能对大家有一点点的帮助与启发,那都算是额外的惊喜了。
在此感激不尽!
本节代码
html代码:
// 欢迎来到 我的世界-
// 基本格式
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行value')
console.log(1);
}, 100)
console.log(2);
})
p.then(value=>{
console.log(3);
},reason=>{
console.log(4);
})
console.log(5);
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) {
const self = this
// 若用户没有传入onResolved箭头函数
if (typeof onResolved !== 'function') {
onResolved = value => value
}
// 若用户没有传入onRejected箭头函数
if (typeof onRejected !== 'function') {
onRejected = resaon => {
throw resaon
}
}
return new Promise((resolve, reject) => {
// 封装common函数
function common(type) {
try {
// 执行函数,并且把执行结果赋值给result变量
let result = type(self.PromiseResult)
if (result instanceof Promise) {
result.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
}
// 成功时的then
if (this.PromiseState === 'fulfilled') {
setTimeout(() => {
common(onResolved)
})
}
// 失败时的then
if (this.PromiseState === 'rejected') {
setTimeout(() => {
common(onRejected);
})
}
// 异步执行的then
if (this.PromiseState === 'pending') {
self.callbacks.push(
{
onResolved: function (data) {
common(onResolved)
},
onRejected: function (data) {
common(onRejected)
}
}
)
}
})
}
// 给构造函数Promise挂载resolve函数
Promise.resolve = function (data) {
return new Promise((resolve, reject) => {
if (data instanceof Promise) {
data.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
resolve(data)
}
})
}
// 给构造函数Promise挂载reject函数
Promise.reject = function (data) {
return new Promise((resolve, reject) => {
reject(data)
})
}
// 挂载catch方法
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
// 挂载all方法
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let count = 0;
let arr = []
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
count++;
arr[i] = v
if (count === promises.length) {
resolve(arr)
}
}, r => {
reject(r)
})
}
})
}
// 挂载race方法
Promise.race = function (promises) {
for (let i = 0; i < promises.length; i++) {
return new Promise((resolve, reject) => {
promises[i].then(v => {
resolve(v)
}, r => {
reject(r)
})
})
}
}