本专栏聚焦Promise的核心原理与高级应用,包含: ✓ Promise A+规范深度解读 ✓ 手写实现与源码分析 ✓ 异步编程设计模式 ✓ 性能调优与错误处理
适合有JavaScript基础,希望深入异步编程的开发者。我们将用最少的篇幅,讲透最核心的知识。
引言:Promise实例方法
在之前的文章中,我们探讨了Promise的状态机模型和不可变原则。理解了Promise的内在状态后,现在让我们转向它的外在行为------实例方法。如果说状态是Promise的"骨骼",那么实例方法就是它的"肌肉",赋予了Promise处理异步操作的能力。
我们可以把Promise就像一台自动咖啡机。状态(pending、fulfilled、rejected)是它的指示灯,而then()、catch()、finally()方法就是它的操作按钮。只有了解每个按钮的确切功能和使用时机,我们才能制作出一杯完美的"异步咖啡"。
then()方法:链式调用的核心
then()的基本语法
Then()方法是为Promise对象添加处理程序的主要方法,也是Promise中最核心、最复杂的方法。它可以接受两个参数:onResolved处理函数 和 onRejected处理函数 。这两个参数都是可选的,如果提供的话,则会在Promise进入 fulfilled 和 rejected 状态时执行,如以下示例:
javascript
function onResolved(id) {
console.log("resolved:" + id)
}
function onRejected(id) {
console.log("rejected:" + id)
}
let p1 = new Promise((resolve, reject) => resolve())
let p2 = new Promise((resolve, reject) => reject())
p1.then(() => onResolved('p1'),
() => onRejected('p1'))
p2.then(() => onResolved('p2'),
() => onRejected('p2'))
上述代码中,由于Promise只能转换一次状态,所以p1和p2这两个操作一定是互斥的,其结果为:
bash
resolved:p1
rejected:p2
由于onResolved处理函数 和 onRejected处理函数 这两个参数都是可选的,当我们只想提供 onRejected处理函数 时,可以在 onResolved 的位置上传入undefined或null,这样可以避免在内存中创建多余的对象,因此then()方法在实际使用中,其传参方式有三种:
javascript
promise.then(onFulfilled) // 只处理成功
promise.then(null, onRejected) // 只处理失败(不推荐)
promise.then(onFulfilled, onRejected) // 同时处理成功和失败
注:
- then()方法中,也可以传递一个非函数,如:promise.then('Hello'),但会被静默忽略,因此不推荐这种写法。
- promise.then(null, onRejected)这种写法也是不推荐的,因为一般默认要处理成功回调。
then()的链式调用
Promise真正的威力来自于then()的链式调用能力,这不仅仅是语法糖,而是一种函数式编程范式的体现。我们来看看下面一个例子:
javascript
// 一个典型的链式调用示例
fetchUserData(userId)
.then(validateUser) // 1. 验证用户数据
.then(enrichWithProfile) // 2. 丰富个人信息
.then(saveToDatabase) // 3. 保存到数据库
.then(notifySubscribers) // 4. 通知订阅者
.then(updateCache) // 5. 更新缓存
.then(finalizeOperation); // 6. 完成操作
链式调用的四种返回值模式
返回普通值
当then()方法中返回一个普通值时,该值会被包装为一个新的Promise对象,如以下示例:
javascript
const myPromise = new Promise((resolve, reject) => {
resolve('Hello');
})
myPromise.then(value => {
console.log('收到:', value);
return '新的普通值'; // 自动包装为 Promise.resolve('新的普通值')
}).then(newValue => {
console.log('链式传递:', newValue); // '新的普通值'
});
返回Promise
当then()方法中返回一个Promise对象时,会等待该Promise完成后,继续下一个then()方法的链式调用,如以下示例:
javascript
const myPromise = new Promise((resolve, reject) => {
resolve('Hello')
})
myPromise.then(value => {
return new Promise((resolve, reject) => {
resolve(value + ' World')
}) // 返回新的Promise
}).then(response => {
// 等待new Promise完成
console.log('处理后的结果:', response) // Hello World
});
抛出异常
当then()方法中抛出异常时,会转换为一个rejected的Promise对象,如以下示例:
javascript
const myPromise = new Promise((resolve, reject) => {
resolve('Hello')
})
myPromise.then(value => {
if (!value.valid) {
throw new Error('数据无效') // 等价于 Promise.reject(new Error('数据无效'))
}
return processValue(value)
}).catch(error => {
console.log('捕获到错误:', error.message) // '数据无效'
})
没有返回或返回undefined
当then()方法中没有返回值,或返回值为undefined时,会将undefined作为一个值,继续传递,如以下示例:
javascript
const myPromise = new Promise((resolve, reject) => {
resolve('Hello')
})
myPromise.then(value => {
console.log('处理值:', value)
// 没有return语句
}).then(nextValue => {
console.log('下一个值:', nextValue) // undefined
})
catch()方法:错误处理
catch()的本质
Catch() 用于给Promise添加拒绝处理程序,它只接受一个参数:onRejected处理函数。实际上,catch()方法是一个语法糖,相当于then(null, onRejected)的语法,它的出现极大地提高了代码的可读性。我们来看看下面一个例子:
javascript
// 以下两种写法完全等价
promise.catch(onRejected)
promise.then(null, onRejected)
// 但.catch()的可读性更好
fetchData()
.then(process)
.catch(handleError) // 清晰:这里处理错误
.then(continueAfterError)
// 对比:使用.then()的第二个参数
fetchData()
.then(process, handleError) // 不清晰:是处理fetch错误还是process错误?
.then(continueAfterError)
错误传播机制
Promise的错误处理遵循冒泡原则:错误会沿着Promise链向后传递,直到被捕获,如以下示例:
javascript
// 错误传播示例
Promise.resolve()
.then(() => {
console.log('步骤1: 成功')
return '第一步结果'
})
.then(result => {
console.log('步骤2: 收到', result)
throw new Error('步骤2发生错误') // 抛出错误
})
.then(result => {
console.log('步骤3: 这行不会执行') // 被跳过
return '第三步结果';
})
.catch(error => {
console.log('捕获到错误:', error.message) // '步骤2发生错误'
return '从错误中恢复';
})
.then(recovery => {
console.log('恢复后继续:', recovery) // '从错误中恢复'
});
finally()方法:资源清理
finally()方法的独特性
Finally()方法用于给Promise实例添加 onFinally处理程序,这个方法可以避免onResolved处理函数 和 onRejected处理函数 中出现冗余代码,无论Promise状态为 fulfilled 或 rejected 的都会执行,即无论Promise是成功还是失败,它都会执行。我们来看看下面的例子:
javascript
// .finally()的基本使用
fetchData()
.then(data => {
console.log('数据处理:', data);
return process(data);
})
.catch(error => {
console.error('处理失败:', error);
throw error; // 重新抛出,让外部知道失败
})
.finally(() => {
console.log('清理资源'); // 无论成功失败都会执行
cleanupResources();
});
finally()方法的三个特性
不接受参数
javascript
promise.finally(() => {
// 这里无法访问Promise的结果或错误原因
console.log('执行清理')
})
原样传递上游的结果或错误
javascript
Promise.resolve('成功数据')
.finally(() => {
console.log('finally执行');
// 这里返回的值不会影响链的传递
return 'finally的返回值会被忽略';
})
.then(value => {
console.log('收到:', value); // '成功数据',不是'finally的返回值'
})
如果finally抛出错误,会覆盖之前的错误
javascript
Promise.resolve('原始数据')
.finally(() => {
throw new Error('finally中的错误'); // 这个错误会覆盖之前的结果
})
.then(value => {
console.log('这不会执行'); // 被跳过
})
.catch(error => {
console.log('捕获的错误:', error.message); // 'finally中的错误'
});
总结
方法对比总结
| 方法 | 主要用途 | 返回值影响 | 执行时机 | 最佳实践 |
|---|---|---|---|---|
| then() | 处理成功结果,链式传递 | 决定下一环的输入 | 前一个Promise完成后 | 保持纯函数,明确返回值 |
| catch() | 错误捕获和恢复 | 可恢复错误或重新抛出 | 链中任何错误发生时 | 在适当层级处理,不要过早吞没错误 |
| finally() | 资源清理和状态重置 | 不影响结果传递 | 无论如何都会执行 | 只做清理,不返回业务数据 |
黄金法则
- 单一职责原则:每个then()应该只做一件事。
- 错误早抛,晚处理:让错误传播到合适的处理层。
- finally()只清理:不要在finally中返回业务逻辑数据。
- 保持链的可读性:合理拆分长链,使用命名函数。
- 考虑可维护性:为复杂的链添加注释和文档。
结语
本文主要介绍了Promise的三种实例化方法,对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!