1. 简介
Promise是用来进行异步编程的。在Promise出现之前,传统的异步编程靠的是监听事件和回调函数,比如:
const xhr = new XMLHttpRequest();
xhr.addEventListener("load", function(data) {
console.log(data);
});
xhr.open("GET", "http://www.example.com/");
xhr.send();
这种方式很可能会产生回调地狱,如果我们想在第一个请求成功并且返回数据后,再去发送第二个请求,那只能在第一个请求成功的回调函数中发送。
以此类推,如果还想再发送下一个请求,就只能在上一个请求成功的回调函数中发送,比如:
const xhr = new XMLHttpRequest();
xhr.addEventListener("load", function(data) {
console.log(data);
const xhr1 = new XMLHttpRequest();
xhr1.open("GET", "http://www.example.com/");
xhr1.send();
xhr1.addEventListener("load", function(data1) {
console.log(data1)
//... 发送第三个请求
});
});
xhr.open("GET", "http://www.example.com/");
xhr.send();
Promise可以有效的帮助我们解决回调地狱问题,可以把Promise理解为一个容器,并且是一个有状态的对象,包括以下3种状态:
- Pending(初始状态)
- Fullfiled
- Rejected
Pending是初始状态,它可以由Pending变成Fullfiled或者Rejected,但是无论变成哪种状态都是不可逆的。
2. Promise的基本用法
2.1 创建Promise对象
// 创建一个promise对象
new Promise(function(resolve, reject) {
...
if(/*异步操作成功*/){
resolve(data);
}else {
reject(err)
}
)
Promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolve和reject,他们也是函数:
- resolve函数会修改状态:Pending -> Fullfiled,成功时调用,并将异步操作的结果作为参数传递出去;
- reject函数会修改状态:Pending -> Rejected,失败时调用,并将异步操作报出的错误作为参数传递出去;
需要注意一个细节:我们传进去的这个函数在创建Promise对象时就会执行。所以,有时我们会将Promise包在一个函数中,在需要的时候才去运行这个函数,比如:
function runAsync() {
return new Promise((resolve, reject) => {
resolve(123);
})
}
runAsync()
我们常见的then、catch、finally方法是添加在Promise构造函数的原型对象上的。
截屏2023-10-30 16.44.55.png
2.2 Promise.prototype.then
当Promise实例生成之后,可以使用then方法分别指定Fullfiled状态和Rejected状态的回调函数:
runAsync().then(function(value) {
// success
}, function(err){
// failure
})
then方法可以接受两个回调函数作为参数:成功回调和失败回调。其中,第二个函数是可选的。这两个函数都接收Promise对象传出的值作为参数。
需要注意的是:then方法指定的回调函数会在触发resolve和reject函数之后执行。
到这里我们已经了解了Promise最常用的使用方式了,在实际的开发中,经常会出现上面提到的回调地狱的情况,Promise可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作,这样就可以解决回调地狱的问题了,在下面会有介绍到。
2.3 Promise.prototype.catch
Promise实例的catch方法,其实和then的第二个参数一样,用来指定reject的回调。用法是这样:
promise.then((data) => {
console.log('resolved',data);
}).catch((err) => {
console.log('rejected',err);
});
效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。可以看下面的代码示例:
p.then((data) => {
console.log('resolved',data);
console.log(somedata); //此处的somedata未定义
})
.catch((err) => {
console.log('rejected',err);
});
在resolve的回调中,我们打印的somedata这个变量是没有定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不会往下运行了。但是在这里,会得到这样的结果:
截屏2023-10-25 20.08.58.png
也就是说进到catch方法里面了,可以捕获到这个错误,不会阻止代码运行。
2.4 Promise.prototype.finally
用于指定不管Promise对象最后状态如何,都会执行的操作。
let promise = new Promise((resolve, reject) => {
resolve(index)
})
promise.then((value)=>{
console.log(value);
}).catch((err)=>{
console.log(err);
}).finally(()=>{
console.log("不管结果如何,都会执行这里的代码");
})
3. 特殊方法(all、race、allSettled、any)
3.1 Promise.all
Promise.all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。简单来说,谁跑的慢就以谁为准执行回调。
Promise.all方法接收一个数组参数,里面的值都是Promise对象,可以看下面的例子:
let Promise1 = new Promise(function(resolve, reject){})
let Promise2 = new Promise(function(resolve, reject){})
let Promise3 = new Promise(function(resolve, reject){})
let p = Promise.all([Promise1, Promise2, Promise3])
p.then(funciton(value){
// 三个都成功则成功
}, function(){
// 只要有失败,则失败
})
有了all,就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。比如一个页面有多个请求,我们可以使用Promise.all方法,同时发出多个请求,在所有的请求都返回数据之后再渲染页面。
需要注意的是,当三个Promise都执行成功后才会回调then方法中的resolve回调函数,参数值是一个数组,包括上面三个Promise执行成功回调的数据。
3.2 Promise.race
谁跑的快,以谁为准执行回调。
race的使用场景:比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:
//请求某个图片资源
function requestImg(){
var p = new Promise((resolve, reject) => {
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = '图片的路径';
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('图片请求超时');
}, 5000);
});
return p;
}
Promise.race([requestImg(), timeout()]).then((data) =>{
console.log(data);
}).catch((err) => {
console.log(err);
});
requestImg函数会异步请求一张图片,我把地址写为"图片的路径",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。运行结果如下:
image.png
4. Promise的链式操作
我们可以看下面的一个例子,当有多层回调时是怎么使用Promise的:
function runAsync(index) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`执行完${index}`)
}, index * 500);
})
}
runAsync(1).then(result=>{
console.log(result);
return runAsync(2)
}).then(result=>{
console.log(result);
return runAsync(3)
}).then(result=>{
console.log(result);
return runAsync(4)
}).then(result=>{
console.log(result);
return "end"
}).then(result=>{
console.log(result);
})
输出:
执行完1
执行完2
执行完3
执行完4
end
在链式操作中,当一个Promise结束后,可以继续返回一个新的Promise,在下一个then方法中继续接收其返回的resolve。
当然,在then方法中,也可以直接return数据而不是Promise对象,在后面的then中也可以接收到数据,可以看上面例子中的最后一个。
5. 手写Promise
上面简单介绍了Promise的使用,下面我们会从最基本的方法开始实现一个Promise。
5.1 Promise基础实现
5.1.1 Promise的基础功能
我们先写一个只有基础功能的例子:
const p = new Promise((resolve, reject) => {
resolve('fulfilled');
reject('rejected');
})
p.then(value => {
console.log(value);
}, reason => {
console.log(reason);
})
// fulfilled
我们来分析一下这段代码都做了什么:
- Promise构造函数会传入一个回调函数作为参数,回调函数又会接受两个参数:resolve和reject,它们都是函数,并且会改变Promise实例对象的状态;
- 一旦状态改变,就不会再改变了。当状态改变之后,会调用then方法的两个回调函数中的一个,并且会相对应的接收resolve或reject函数的参数值;
- fulfilled状态下会执行then方法的第一个回调函数,rejected状态会执行第二个回调函数;
理解了这段代码的执行逻辑之后,我们可以分两步实现Promise的基础功能,分别是:new Promise的实现原理和then方法的实现原理。
5.1.2 new Promise的实现原理
<script>
const status_Pending = "pending"
const status_Fulfilled = "fulfilled"
const status_Rejected = "rejected"
class CustomPromise {
// 初始状态
status = status_Pending
// 成功之后的值
value = null
// 失败之后的原因
reason = null
constructor(executor) {
// 将resolve和reject传给new Promise的回调函数
executor(this.resolve, this.reject)
}
// resolve
resolve = (value) => {
// 改变状态为fulfilled
if (this.status === status_Pending) {
this.status = status_Fulfilled
}
}
// rejected
reject = (reason) => {
// 改变状态为 rejected
if (this.status === status_Pending) {
this.status = status_Rejected
}
}
}
</script>
这里有两个注意点:
- resolve和reject方法只有在箭头函数的情况下,才能直接传递给executor函数作为参数,这样在外部调用resolve或reject函数的时候,this指向始终是Promise实例对象。如果改为普通函数也可以,就是需要使用bind方法来稳定resolve和reject的this指向:executor(this.resolve.bind(this), this.reject.bind(this))
- resolve和reject只有在pending状态下,蔡需要改变状态和记录结果;
JavaScript中,bind方法用于创建一个新的函数,该函数的this值会被指定为一个特定的对象,并且在调用时,指定的参数将作为新函数的参数列表。
https://baijiahao.baidu.com/s?id=1761949446370869152&wfr=spider&for=pc
5.1.3 then方法的实现原理
class CustomPromise {
...省略部分代码
// resolve
resolve = (value) => {
// 改变状态为fulfilled
if (this.status === status_Pending) {
this.status = status_Fulfilled
this.value = value
}
}
// rejected
reject = (reason) => {
// 改变状态为 rejected
if (this.status === status_Pending) {
this.status = status_Rejected
this.reason = reason
}
}
...省略部分代码
}
5.1.4 基础功能的完整代码
<script>
const status_Pending = "pending"
const status_Fulfilled = "fulfilled"
const status_Rejected = "rejected"
class CustomPromise {
// 初始状态
status = status_Pending
// 成功之后的值
value = null
// 失败之后的原因
reason = null
constructor(executor) {
// 将resolve和reject传给new Promise的回调函数
executor(this.resolve, this.reject)
}
// resolve
resolve = (value) => {
// 改变状态为fulfilled
if (this.status === status_Pending) {
this.status = status_Fulfilled
this.value = value
}
}
// rejected
reject = (reason) => {
// 改变状态为 rejected
if (this.status === status_Pending) {
this.status = status_Rejected
this.reason = reason
}
}
then(onFulfilled, onRejected) {
if (this.status === status_Fulfilled) {
// 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。
onFulfilled(this.value)
} else if (this.status === status_Rejected) {
// 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。
onRejected(this.reason)
}
}
}
</script>
我们用一开始的例子来测试一下:
const promise = new CustomPromise((resolve, reject) => {
resolve("value")
})
promise.then(value => {
console.log(value);
},reason => {
});
// value
Promise 基础功能的实现原理顺利完成。
5.2 处理异步逻辑
基础版本的Promise有个缺点,就是无法处理异步的情况。
const p = new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve("fulfilled");
});
});
p.then(value => {
console.log(value);
}, reason => {
console.log(reason);
})
// 不会输出任何信息
由于使用了setTimeout执行resolve函数,导致then方法执行比resolve函数要早,所以then方法在执行的时候,Promise的状态是Pending,不会执行任何回调函数。
基于这种情况,我们需要在then方法中添加处理pending状态的逻辑:如果在then方法中判断状态是pending,那么就先将两个回调函数保存起来,然后在Promise内部的resolve或reject方法中执行。
<script>
const status_Pending = "pending"
const status_Fulfilled = "fulfilled"
const status_Rejected = "rejected"
class CustomPromise {
// 初始状态
status = status_Pending
// 成功之后的值
value = null
// 失败之后的原因
reason = null
// 保存onFulfilled回调函数
onFulfilledCallback = null
// 保存 onRejected 回调函数
onRejectedCallback = null
constructor(executor) {
// 将resolve和reject传给new Promise的回调函数
executor(this.resolve, this.reject)
}
// resolve
resolve = (value) => {
// 改变状态为fulfilled
if (this.status === status_Pending) {
this.status = status_Fulfilled
this.value = value
// 执行 onFulfilled 回调函数
if (this.onFulfilledCallback) {
this.onFulfilledCallback(value)
}
}
}
// rejected
reject = (reason) => {
// 改变状态为 rejected
if (this.status === status_Pending) {
this.status = status_Rejected
this.reason = reason
// 执行 onRejected 回调函数
if (this.onRejectedCallback) {
this.onRejectedCallback(reason)
}
}
}
then(onFulfilled, onRejected) {
if (this.status === status_Fulfilled) {
// 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。
onFulfilled(this.value)
} else if (this.status === status_Rejected) {
// 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。
onRejected(this.reason)
} else {
// pending 状态下保存回调函数
onFulfilledCallback = onFulfilled
onRejectedCallback = onRejected
}
}
}
const promise = new CustomPromise((resolve, reject) => {
resolve("value")
})
promise.then(value => {
setTimeout(() => {
console.log(value);
}, 1000);
},reason => {
});
</script>
这样,我们就能处理异步逻辑了。
5.3 then方法的多次调用
别忘了,Promise实例对象的then方法是可以多次调用的,而我们现在的代码是无法做到这一点的:
const p = new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve("fulfilled");
});
});
p.then((value) => {
console.log("1", value);
});
p.then((value) => {
console.log("2", value);
});
p.then((value) => {
console.log("3", value);
});
// 3 fulfilled
目前的代码只能输出 3 fulfilled。为什么只能输出 3 fulfilled 呢?关键在于源码当中 then 方法保存回调函数的方式:
class CustomPromise {
//... 省略部分代码
then(onFulfilled, onRejected) {
if (this.status === status_Fulfilled) {
onFulfilled(this.value);
} else if (this.status === status_Rejected) {
onRejected(this.reason);
} else {
// pending 状态下保存回调函数
this.onFulfilledCallback = onFulfilled;
this.onRejectedCallback = onRejected;
}
}
}
回调函数会存在成员变量中,这样就会导致保存的是最后一个then方法的回调函数,所以这里应该使用数组来保存所有的回调函数,同时Promise内部的resolve和reject方法也需要循环调用所有的回调函数:
<script>
const status_Pending = "pending"
const status_Fulfilled = "fulfilled"
const status_Rejected = "rejected"
class CustomPromise {
// 初始状态
status = status_Pending
// 成功之后的值
value = null
// 失败之后的原因
reason = null
// 保存onFulfilled回调函数
onFulfilledCallbacks = []
// 保存 onRejected 回调函数
onRejectedCallbacks = []
constructor(executor) {
// 将resolve和reject传给new Promise的回调函数
executor(this.resolve, this.reject)
}
// resolve
resolve = (value) => {
// 改变状态为fulfilled
if (this.status === status_Pending) {
this.status = status_Fulfilled
this.value = value
// 执行所有的 onFulfilled 回调函数
this.onFulfilledCallbacks.forEach(callback => {
callback(this.value)
});
}
}
// rejected
reject = (reason) => {
// 改变状态为 rejected
if (this.status === status_Pending) {
this.status = status_Rejected
this.reason = reason
// 执行所有的 onRejected 回调函数
this.onRejectedCallbacks.forEach(callback => {
callback(this.reason)
});
}
}
then(onFulfilled, onRejected) {
if (this.status === status_Fulfilled) {
// 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。
onFulfilled(this.value)
} else if (this.status === status_Rejected) {
// 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。
onRejected(this.reason)
} else {
// pending 状态下保存回调函数
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
}
}
const promise = new CustomPromise((resolve, reject) => {
resolve("value")
})
promise.then(value => {
setTimeout(() => {
console.log(value);
}, 1000);
},reason => {
});
</script>
这其实是一个「观察者模式」,then 方法的 this.onFulfilledCallbacks.push(onFulfilled) 和 this.onRejectedCallbacks.push(onRejected) 就是在添加订阅者,而 resolve 和 reject 方法就是在通知所有的订阅者。
我们再运行一下用例,得出结果:
1 fulfilled
2 fulfilled
3 fulfilled
5.4 then方法的链式调用
Promise最核心的功能就是then方法的链式调用,这也是解决回调地狱的关键。我们目前手动实现的代码是不能进行then方法的链式调用的,因为then方法没有任何返回值。
要想实现then方法的链式调用,then方法必须返回Promise对象,并且下一个then方法的回调函数的参数会依赖上一个then方法的回调函数的返回值,这种依赖有两种情况:
- 如果返回的是Promise对象,那么下一个then方法的回调函数会接受该实例对象的resolve或reject函数传入的值,比如:
const p = new Promise((resolve, reject) => {
resolve(1);
});
p.then((value) => {
console.log(value);
return new Promise((resolve, reject) => {
resolve(2);
// reject(2);
});
}).then((value) => {
console.log("fulfilled", value);
}, (err) => {
console.log("rejected", err);
});
// 1
// fulfilled 2
// 如果调用的是 reject(2),那么返回的是:
// 1
// rejected 2
- 如果返回的是 thenable 对象,会和第一种情况一样:
const p = new Promise((resolve, reject) => {
resolve(1);
});
p.then((value) => {
console.log(value);
return {
then(resolve, reject) {
resolve(2);
// reject(2);
}
};
}).then((value) => {
console.log("fulfilled", value);
}, (err) => {
console.log("rejected", err);
});
// 1
// fulfilled 2
// 如果调用的是 reject(2),那么返回的是:
// 1
// rejected 2
- 如果返回的是其他对象或者原始数据类型的值,那么下一个then方法的回调函数的参数会直接接收这个值,比如:
const p = new Promise((resolve, reject) => {
resolve(1);
});
p.then((value) => {
console.log(value);
return {
value: 2,
};
}).then((value) => {
console.log("fulfilled", value);
});
// 1
// fulfilled { value: 2 }
了解了then方法链式调用的基本情况后,我们手动实现一下then方法的链式调用。
首先,让then方法返回一个Promise对象:
then(onFulfilled, onRejected) {
const promise2 = new CustomPromise((resolve, reject) => {
if (this.status === status_Fulfilled) {
// 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。
onFulfilled(this.value)
} else if (this.status === status_Rejected) {
// 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。
onRejected(this.reason)
} else {
// pending 状态下保存回调函数
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
})
return promise2
}
接着,通过resolve和reject函数来改变promise2的状态,并且建立上一个then和下一个then的依赖关系:
<script>
const status_Pending = "pending"
const status_Fulfilled = "fulfilled"
const status_Rejected = "rejected"
class CustomPromise {
// 初始状态
status = status_Pending
// 成功之后的值
value = null
// 失败之后的原因
reason = null
// 保存onFulfilled回调函数
onFulfilledCallbacks = []
// 保存 onRejected 回调函数
onRejectedCallbacks = []
constructor(executor) {
// 将resolve和reject传给new Promise的回调函数
executor(this.resolve, this.reject)
}
// resolve
resolve = (value) => {
// 改变状态为fulfilled
if (this.status === status_Pending) {
this.status = status_Fulfilled
this.value = value
// 执行所有的 onFulfilled 回调函数
this.onFulfilledCallbacks.forEach(callback => {
callback(this.value)
});
}
}
// rejected
reject = (reason) => {
// 改变状态为 rejected
if (this.status === status_Pending) {
this.status = status_Rejected
this.reason = reason
// 执行所有的 onRejected 回调函数
this.onRejectedCallbacks.forEach(callback => {
callback(this.reason)
});
}
}
then(onFulfilled, onRejected) {
const promise2 = new CustomPromise((resolve, reject) => {
if (this.status === status_Fulfilled) {
// 获取上一个then方法的fulfilled回调函数的返回值
const v = onFulfilled(this.value)
// 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
resolvePromise(v, resolve, reject)
} else if (this.status === status_Rejected) {
// 获取上一个then方法的rejected回调函数的返回值
const v = onRejected(this.reason)
// 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
resolvePromise(v, resolve, reject)
} else {
// pending 状态下保存回调函数
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
})
return promise2
}
resolvePromise(value, resolve, reject) {
if (typeof value === "object" || typeof value === "function") {
if (value === null) {
// 如果返回值是null,直接调用resolve函数,promise2的状态变为fulfilled
resolve(value)
}
try {
if (typeof value.then === "function") {
// 如果返回值是 Promise 对象或者 thenable 对象
// 那就只能交给它们的 then 方法来改变 promise2 的状态,以及获取相对应的状态值
// 以下代码等同于 value.then((value) => resolve(value), (err) => reject(err))
value.then(resolve, reject)
} else {
// 如果 then 不是函数,同 null 情况一样的处理逻辑。
resolve(value);
}
} catch (error) {
// 出现异常的情况下,调用 reject 函数
// promise2 的状态变为 rejected,
// 错误信息由下一个 then 方法的第二回调函数接收
reject(error);
}
} else {
// 如果返回值是其他对象或者原始数据类型值,同 null 情况一样的处理逻辑。
resolve(value);
}
}
}
</script>
这里比较难理解的是,返回值为Promise对象或者thenable对象的处理情况:value.then(resolve, reject),这段代码写完整一点就是:
value.then((value) => resolve(value), (err) => reject(err))
这里 then 方法的回调函数中 value 参数值和 err 参数值就是 Promise 对象或者 thenable 对象内部调用 resolve 或者 reject 函数传入的参数值,再把这些值传递给 promise2 的 resolve 和 reject 函数,从而达到改变 promise2 的状态,下一个 then 方法的回调函数也会被调用并且接收到这些值。
总得来说就是,promise2 的状态完全由返回值(Promise 对象或者 thenable 对象)来控制。就跟以下这段代码一样:
const promise2 = new CustomPromise((resolve2, reject2) => {
const value = new CustomPromise((resolve, reject) => {
resolve(1);
});
value.then(
(v) => resolve2(v),
(err) => reject2(err)
);
});
promise2.then((value) => {
console.log(value);
});
// 1
我们执行一下测试用例,看我们自己写的Promise是否可以正常工作:
const p = new CustomPromise((resolve, reject) => {
resolve(1);
});
p.then((value) => {
console.log(value);
return new CustomPromise((resolve) => {
resolve(2);
});
}).then((value) => {
console.log(value);
});
// 1
// 2
p.then((value) => {
console.log(value);
return {
then(resolve) {
resolve(2);
}
};
}).then((value) => {
console.log(value);
});
// 1
// 2
p.then((value) => {
console.log(value);
return 2;
};
}).then((value) => {
console.log(value);
});
// 1
// 2
那如果 then 方法是返回自身的 Promise 对象该怎么办?我们来看看原生的 Promise 是怎么处理的:
const p = new Promise((resolve, reject) => {
resolve(1);
});
const p1 = p.then(value => {
console.log(value);
return p1;
});
// 1
// Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
报错,错误信息是:会发生 Promise 循环调用。
所以,我们需要改造一下 SelfPromise 的代码,来模拟这种报错的效果:
then(onFulfilled, onRejected) {
const promise2 = new CustomPromise((resolve, reject) => {
if (this.status === status_Fulfilled) {
// 获取上一个then方法的fulfilled回调函数的返回值
const v = onFulfilled(this.value)
// 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
// 将 promise2 传入进行判断
this.resolvePromise(promise2, v, resolve, reject)
} else if (this.status === status_Rejected) {
// 获取上一个then方法的rejected回调函数的返回值
const v = onRejected(this.reason)
// 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
// 将 promise2 传入进行判断
this.resolvePromise(promise2, v, resolve, reject)
} else {
// pending 状态下保存回调函数
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
})
return promise2
}
resolvePromise(promise2, value, resolve, reject) {
// 如果 then 方法返回的是自身 Promise 对象,返回错误信息
if (promise2 === value) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
// 如果then方法返回的是自身Promise对象,返回错误信息
if (typeof value === "object" || typeof value === "function") {
if (value === null) {
// 如果返回值是null,直接调用resolve函数,promise2的状态变为fulfilled
return resolve(value)
}
try {
if (typeof value.then === "function") {
// 如果返回值是 Promise 对象或者 thenable 对象
// 那就只能交给它们的 then 方法来改变 promise2 的状态,以及获取相对应的状态值
// 以下代码等同于 value.then((value) => resolve(value), (err) => reject(err))
value.then(resolve, reject)
} else {
// 如果 then 不是函数,同 null 情况一样的处理逻辑。
resolve(value);
}
} catch (error) {
// 出现异常的情况下,调用 reject 函数
// promise2 的状态变为 rejected,
// 错误信息由下一个 then 方法的第二回调函数接收
reject(error);
}
} else {
// 如果返回值是其他对象或者原始数据类型值,同 null 情况一样的处理逻辑。
resolve(value);
}
}
}
我们使用自己构建的CustomPromise尝试下之前的例子:
const p = new CustomPromise((resolve, reject) => {
resolve(1);
});
const p1 = p.then(value => {
console.log(value);
return p1;
});
发现错误:
截屏2023-10-30 17.27.02.png
发现错误信息不一样,这里根据提示可以知道,我们在p1定义之前就使用了它。实际情况也确实如此,我们先等then方法里面的回调函数执行完毕之后,then方法再返回Promise对象,但我们却在回调函数内先用了这个Promise对象,所以才报这个错误信息。
那怎么解决呢?其实只需要把then方法的回调函数的同步执行改为异步执行就可以了,这里我使用setTimeout进行处理:
then(onFulfilled, onRejected) {
const promise2 = new CustomPromise((resolve, reject) => {
if (this.status === status_Fulfilled) {
setTimeout(() => {
// 获取上一个then方法的fulfilled回调函数的返回值
const v = onFulfilled(this.value)
// 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
// 将 promise2 传入进行判断
this.resolvePromise(promise2, v, resolve, reject)
});
} else if (this.status === status_Rejected) {
setTimeout(() => {
// 获取上一个then方法的rejected回调函数的返回值
const v = onRejected(this.reason)
// 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
// 将 promise2 传入进行判断
this.resolvePromise(promise2, v, resolve, reject)
});
} else {
// pending 状态下保存回调函数
// pending 状态下保存回调函数
this.onFulfilledCallbacks.push((value)=>{
setTimeout(() => {
onFulfilled(value)
})
})
this.onRejectedCallbacks.push((reason)=>{
setTimeout(() => {
onRejected(reason)
})
})
}
})
return promise2
}
测试通过:
const p = new CustomPromise((resolve, reject) => {
resolve('1')
})
// 返回自身 Promise 对象
const p1 = p.then(value => {
console.log(value);
return p1;
})
// 接收错误信息
p1.then(value => {
console.log(2);
console.log('fulfilled', value)
}, err => {
console.log(3);
console.log('reject', err.message);
})
// 1
// 3
// reject Chaining cycle detected for promise #<Promise
5.5 catch方法实现原理
catch 方法是用于指定发生错误时的回调函数,它其实就是对 then 方法的调用,想想我们之前是通过 then 方法的第二个参数来接收 rejected 状态的错误:
const p = new CustomPromise((resolve, reject) => {
reject(1);
})
p.then((value) => {
console.log("fulfilled", value);
}, (reason) => {
console.log("rejected", reason);
});
// rejected 1
所以,catch 方法等同于 then(null, onRejected) 或 then(undefined, onRejected),因此实现原理也很明了:
// ...省略部分代码
class CustomPromise {
// ...省略部分代码
catch(onRejected) {
return this.then(null, onRejected);
}
}
5.5 finally 方法实现原理
finally方法也是一个语法糖,它用于在Promise实例上注册一个处理函数,无论Promise是成功还是失败,该处理函数都会被调用:
class CustomPromise {
// ...其他代码
finally(callback) {
// 调用then方法,传入两个相同的处理函数
return this.then(
value => {
// 创建一个新的Promise实例,确保异步执行callback
return CustomPromise.resolve(callback()).then(() => value);
},
reason => {
// 创建一个新的Promise实例,确保异步执行callback
return CustomPromise.resolve(callback()).then(() => { throw reason; });
}
);
}
}
我们看到上面的方法实现中依赖静态的resolve和reject方法,在下面会有介绍。
5.6 Promise.resolve、Promise.reject静态方法
这两个方法可以快速的创建一个已经解决或拒绝的Promise实例:
static resolve(value) {
if (value instanceof CustomPromise) {
return value
}
return new CustomPromise((resolve, reject) => {
resolve(value)
})
}
static reject(value) {
return new CustomPromise((resolve, reject) => {
reject(value)
})
}
这一步中,我们实现了resolve和reject静态方法,resolve方法接收一个参数:value,用于创建一个已经解决的Promise实例。reject方法接收一个参数:reason,用于创建一个已经拒绝的Promise实例。
网友评论