一,前言
上一篇,实现了 Promise 对异步操作的支持,主要涉及以下几个点:
- 测试 Promise 对异步操作的支持;
- 分析当前 Promise 代码问题及解决方案;
- 使用发布订阅思想实现对异步操作的支持;
- Promise 异步操作的测试;
本篇,继续将实现 Promise 的链式调用;
二,前文回顾
在前几篇中,简单介绍了 Promise 的使用;翻译 Promise A+ 规范;实现简版 Promise;
上一篇,利用发布订阅模式,实现了 Promise 对异步操作的支持,主要就是以下两步:
- 先将 then 方法中的成功/失败回调收集起来;
- 当 promise 状态改变时(调用 resolve 或 reject),再依次执行对应的回调处理;
三,提出问题
Promise 的一个核心特性,就是支持链式调用;
也因此,使用 Promise 能够很好的解决异步逻辑的“回调地狱”问题;
备注:
- 关于 then 函数的返回值(也就是 Promise A+ 规范中提到的 x)存在多种返回值类型需要进行处理,将在下一篇展开详细的介绍;
- 本篇主要以实现 Promise 链式调用功能为目的;暂不考虑返回值为 promise 的情况;
1,测试原生 Promise
- 所谓的“普通值”,需要满足以下几点:
- 不能是 Promise 对象;
- 不能是报错或抛出异常,必须是能够正常返回的;比如 throw new Error() 就不属于普通值;
- 成功或失败的回调处理没有返回值,相当于返回 undefined,属于普通值;
情况 1,成功回调中,返回普通值
let promise = new Promise((resolve, reject) => {
setTimeout(() => { // 异步操作
resolve('执行成功'); // 进入成功回调处理
}, 1000)
})
promise.then((value) => {
console.log('[then1-onFulfilled]', value)
return "then1-onFulfilled:" + value
// return new Error() // 返回普通值
// return undefined // 返回普通值
}, (reson) => {
console.log('[then1-onRejected]', reson)
}).then((value) => {
console.log('[then2-onFulfilled]', value)
}, (reson) => {
console.log('[then2-onRejected]', reson)
})
// [then1-onFulfilled] 执行成功
// [then2-onFulfilled] then1-onFulfilled:执行成功
- 现象:
- 1 秒后,执行了 then1 中的 onFulfilled 回调,继续 return value;
- 然后,进入 then2 中的 onFulfilled 回调,可以获取到 then1 传递的 value;
- 结论:
- 成功回调中返回普通值,会进入下一个 then 的成功回调;
情况 2,失败回调中,返回普通值
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("执行失败") // 进入失败回调处理
}, 1000)
})
promise.then((value) => {
console.log('[then1-onFulfilled]', value)
return "then1-onFulfilled:" + value
}, (reson) => {
console.log('[then1-onRejected]', reson)
return "then1-onRejected:" + reson // 返回普通值
// return new Error() // 返回普通值
// return undefined // 返回普通值
}).then((value) => {
console.log('[then2-onFulfilled]', value)
}, (reson) => {
console.log('[then2-onRejected]', reson)
})
// [then1-onRejected] 执行失败
// [then2-onFulfilled] then1-onRejected:执行失败
-
现象:
- 1 秒后,执行了 then1 中的 onRejected 回调,继续 return reson;
- 然后,进入 then2 中的 onFulfilled 回调,可以获取到 then1 传递的 value;(这个value 就是 then1 中 return 的普通值)
-
结论:
- 失败回调中返回普通值,会进入下一个 then 的成功回调;
-
备注:
- 由于每个 promise 实例的状态只能被修改一次,而示例中失败态的 promise 在第二次 then 时进入了成功的回调,说明 then1 处理完成后,返回了一个全新的 promise 实例;
情况 3,成功回调中,抛出异常
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("执行成功")
}, 1000)
})
promise.then((value) => {
console.log('[then1-onFulfilled]', value)
throw new Error("抛出异常")
}, (reson) => {
console.log('[then1-onRejected]', reson)
return "then1-onRejected:" + reson
}).then((value) => {
console.log('[then2-onFulfilled]', value)
}, (reson) => {
console.log('[then2-onRejected]', reson)
})
// [then1-onFulfilled] 执行成功
// [then2-onRejected] Error: 抛出异常
- 现象:
- 1 秒后,执行了 then1 中的 onFulfilled 回调,抛出异常;
- 然后,进入 then2 中的 onRejected 回调,并捕获到 then1 抛出的异常;
- 结论:
- 成功回调中抛出异常,会进入下一个 then 的失败回调;
情况 4,失败回调中,抛出异常
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("执行失败")
}, 1000)
})
promise.then((value) => {
console.log('[then1-onFulfilled]', value)
}, (reson) => {
console.log('[then1-onRejected]', reson)
throw new Error("抛出异常")
}).then((value) => {
console.log('[then2-onFulfilled]', value)
}, (reson) => {
console.log('[then2-onRejected]', reson)
})
// [then1-onRejected] 执行失败
// [then2-onRejected] Error: 抛出异常
- 现象:
- 1 秒后,执行了 then1 中的 onRejected 回调,抛出异常;
- 然后,进入 then2 中的 onRejected 回调,并捕获到 then1 抛出的异常;
- 结论:
- 失败回调中抛出异常,会进入下一个 then 的失败回调;
情况 5,executor 执行器中报错
如果在executor中就抛出了异常,还没有执行 resolve 和 reject:
备注:同步逻辑报错,非异步操作;
let promise = new Promise((resolve, reject) => {
throw new Error("executor 同步报错") // 抛出异常
resolve("处理成功") // 不会被执行
})
promise.then((value) => {
console.log('[then1-onFulfilled]', value)
}, (reson) => {
console.log('[then1-onRejected]', reson)
}).then((value) => {
console.log('[then2-onFulfilled]', value)
}, (reson) => {
console.log('[then2-onRejected]', reson)
})
// [then1-onRejected] Error: executor 同步报错
// [then2-onFulfilled] undefined
- 现象:
- executor 执行器函数中的同步操作抛出异常,进入 then1 的失败回调;
- 由于 then1 中没有 return,相当于返回了 undefined 普通值,进入 then2 的成功处理;
- 结论:
- executor同步逻辑抛出异常,进入 then 失败处理;
总结
根据以上 5 种情况进行总结:
- Promise 的链式调用,当调用 then 方法后,将会返回一个全新的 promise 实例;
- then 中方法(无论成功还是失败)返回普通值,都会做为下一次 then 的成功结果;
- 执行器函数 和 then 中方法(无论成功还是失败)抛出异常,都会进入下一次 then 的失败结果;
备注:
- 若 then 中方法返回一个 promise 实例,那么,将根据这个 promise 的状态决定执行成功还是失败的回调:
- 成功的 promise:将返回值传递给下一个 then 的成功回调;
- 失败的 promise:将失败原因传递给下一个 then 的失败回调;
2,测试手写 Promise
链式调用示例(与测试原生 Promise 代码相同):
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行成功');
}, 1000)
})
promise.then((value) => {
console.log('[then1-onFulfilled]', value)
return "then1-onFulfilled:" + value
}, (reson) => {
console.log('[then1-onRejected]', reson)
}).then((value) => {
console.log('[then2-onFulfilled]', value)
}, (reson) => {
console.log('[then2-onRejected]', reson)
})
// TypeError: Cannot read property 'then' of undefined
现象
- 会在第二个 then 方法调用处抛出异常;
3,问题分析
- 现象:手写的 Promise 源码,目前只支持单次调用 then 方法,多次调用就会报错;
- 原因:当前 Promise 在调用 then 方法后,没有返回新的 Primise 对象,所以不能继续
.then
;
class Promise{
constructor(executor){
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const reslove = (value) =>{...}
const reject = (reason) =>{...}
executor(reslove, reject);
}
then(onFulfilled, onRejected){
if(this.state === PENDING){
this.onResolvedCallbacks.push(()=>{
onFulfilled(this.value) // 被调用后,没有返回新的 promise
})
this.onRejectedCallbacks.push(()=>{
onRejected(this.value) // 被调用后,没有返回新的 promise
})
}
if(this.state === DULFILLED){
onFulfilled(this.value) // 被调用后,没有返回新的 promise
}
if(this.state === REJECTED){
onRejected(this.reason) // 被调用后,没有返回新的 promise
}
// then 处理完成后,并没有继续返回 promise 对象,所以不能继续 .then
}
}
module.exports = Promise;
4,解决方案
在 then 方法的回调函数执行完成后,需要获取到函数的返回值,并包装成为全新的 promise 实例;
- 获取 then 回调处理的返回值;(这个返回值,就是Promise A+ 规范中的 x)
- 包装成为全新的 promise 实例;
- 异常处理:executor 和 then 方法中 添加 try..catch... 进行异常捕获;
四,Promise 链式调用的实现
1,获取 then 回调处理返回值
class Promise{
constructor(executor){...}
then(onFulfilled, onRejected){
if(this.state === DULFILLED){
// 拿到回调处理的返回值
let x = onFulfilled(this.value)
}
if(this.state === REJECTED){
let x = onRejected(this.reason)
resolve(x)
}
if(this.state === PENDING){
this.onResolvedCallbacks.push(()=>{
let x = onFulfilled(this.value)
resolve(x)
})
this.onRejectedCallbacks.push(()=>{
let x = onRejected(this.reason)
resolve(x)
})
}
// 包装回调处理的返回值 x ,成为一个全新的 Promise 实例
let promise2 = new Promise((resolve, reject)=>{
// 问题:拿不到 x,不在一个作用域中
});
// 返回新的 promise 实例,这样外部可以继续调用 .then
return promise2;
}
}
问题
- then 函数返回新创建的 promise2,但 promise2 内部是拿不到返回值 x 的;
2,then 返回全新 promise
要解决上边的问题,就需要在 promise2 的 executor 执行器中拿到返回值 x;
class Promise{
constructor(executor){...}
then(onFulfilled, onRejected){
// 获取成功/失败回调的返回值,并包装成为新的 Promise 实例
let promise2 = new Promise((resolve, reject)=>{
// 同步
if(this.state === DULFILLED){
// 拿到回调处理的返回值
let x = onFulfilled(this.value)
// 更改 promise2 的状态,并返回这个新的 promise 实例(即 primise2)
resolve(x)
}
// 同步
if(this.state === REJECTED){
let x = onRejected(this.reason)
resolve(x)
}
// 异步
if(this.state === PENDING){
this.onResolvedCallbacks.push(()=>{
let x = onFulfilled(this.value)
resolve(x)
})
this.onRejectedCallbacks.push(()=>{
let x = onRejected(this.reason)
resolve(x)
})
}
});
// 返回新的 promise 实例
return promise2;
}
}
3,对异常的处理
executor 执行器函数和 then 中方法(无论成功还是失败)抛出异常,都会进入下一次 then 的失败结果;
只需在以下几个位置捕获异常,并执行 reject 即可:
- executor 执行器函数(在简版 Promsie 中已实现)
- then 方法中的同步操作(DULFILLED 和 REJECTED),回调函数执行时;
- then 方法中的异步操作(PENDING),回调函数执行时;
class Promise{
constructor(executor){
const reslove = (value) =>{...}
const reject = (reason) =>{...}
try{
executor(reslove, reject);
}catch(e){
reject(e) // 捕获异常
}
}
then(onFulfilled, onRejected){
let promise2 = new Promise((resolve, reject)=>{
if(this.state === DULFILLED){
try{
let x = onFulfilled(this.value)
resolve(x)
}catch(e){
reject(e) // 捕获异常
}
}
if(this.state === REJECTED){
try{
let x = onRejected(this.reason)
resolve(x)
}catch(e){
reject(e) // 捕获异常
}
}
if(this.state === PENDING){
this.onResolvedCallbacks.push(()=>{
try{
let x = onFulfilled(this.value)
resolve(x)
}catch(e){
reject(e) // 捕获异常
}
})
this.onRejectedCallbacks.push(()=>{
try{
let x = onRejected(this.reason)
resolve(x)
}catch(e){
reject(e) // 捕获异常
}
})
}
});
return promise2;
}
}
4,测试手写 Promsie 的链式调用
let promise = new Promise((resolve, reject) => {
setTimeout(() => { // 异步操作
resolve('执行成功'); // 成功态,传参
}, 1000)
})
promise.then((value) => {
console.log('[then1-onFulfilled]', value)
return "then1-onFulfilled:" + value
}, (reson) => {
console.log('[then1-onRejected]', reson)
}).then((value) => {
console.log('[then2-onFulfilled]', value)
}, (reson) => {
console.log('[then2-onRejected]', reson)
})
// [then1-onFulfilled] 执行成功
// [then2-onFulfilled] then1-onFulfilled:执行成功
略:5 种情况与原生 Promise 表现一致;
五,执行过程分析
分析当前 Promise 的执行过程:
- 首先,应用层 new Promise 创建 promise 实例时,传入的 executor 执行器会在 constructor 中被立即执行;
- 应用层调用
promise.then
时,传入成功/失败状态的处理逻辑; - 进入 then 处理,在源码中,会先创建一个 promise2 实例;那么,promise2 的 executor 执行器也会被立即执行;
- 此时,原本 promise 对三态的处理,就被包裹在了 promise2 的 executor 中;因此,从执行上不会受到影响,同样会被立即执行;
- 这样,在包装 then 返回值 x 的 promise2 中,就可以拿到这个返回值 x 了!
- 当 promise2 调用了 resolve(x),就会进入外部
promise.then
的成功回调处理,并返回一个普通值; - 源码中的 promise2 中就拿到了
promise.then
的返回值 x,并通过 reslove(x) 将 promise2 的状态更新为成功态; - 然后,就进入了应用层 Promise 链式调用第二个 then 的成功回调处理。。。。
备注:
- 存在递归操作:then 方法中的回调处理将返回一个 promise,promise 继续 then,继续返回 promise。。。
六,结尾
本篇,主要实现了 Promise 的链式调用功能,主要涉及以下几个点:
- 介绍了 Promise 链式调用,返回普通值和抛出异常的5种情况;
- 分析了当前 Promise 源码的问题及解决方案;
- Promise 链式调用的实现、功能测试、执行过程分析;
备注:
- 由于当前并未考虑到返回值 x 为 promise 的情况,所以代码并非最终实现;
- 下一篇集中处理返回值 x,即实现 Promise 源码中的核心方法:
resolvePromise
;
下一篇,实现 Promise 返回值 x 的处理;
维护日志
- 20211031
- 添加了原生 Promise 链式调用的 5 种情况和总结;
- 添加了对 “Promsie 异常处理部分”的说明;
- 添加了“执行过程分析”内容,解释了 then 中嵌入 promise 2 后的变化;
- 重新梳理一二级目录,添加必要的备注和代码注释,修改错别字,修改文章摘要;
- todo 关联 Promise A+ 规范内容;
- 20211101
- todo 补充 then 穿透的分析和实现;