JavaScript Promise:then() 与 catch() 链式调用
JavaScript 中的 Promise 链式调用是处理异步操作的核心机制。通过 then() 和 catch() 的串联,可以将复杂的异步逻辑转化为线性的、同步感极强的代码结构。掌握链式调用的数据流转与错误冒泡机制,是编写健壮异步代码的关键。
1. 理解链式调用的基础机制
Promise 链式调用的核心在于:每一个 then() 方法或 catch() 方法在执行后,都会返回一个新的 Promise 对象。这使得我们可以无限地串联后续操作,而不会陷入回调地狱。
- 创建一个基础的 Promise 对象。
- 调用第一个
.then()方法并传入回调函数。 - 观察返回值:无论回调函数内部执行了什么操作,
.then()都会返回一个新的 Promise 状态(Pending、Fulfilled 或 Rejected),供下一次调用使用。
以下代码展示了最基本的链式结构:
const promise = new Promise((resolve, reject) => {
resolve(1);
});
promise
.then((value) => {
console.log(value); // 输出 1
return value + 1;
})
.then((value) => {
console.log(value); // 输出 2
return value + 1;
});
2. 掌握 then() 中的数据穿透规则
在 .then() 中,回调函数的返回值决定了下一个 .then() 接收到的参数。理解这一规则是控制数据流向的关键。
- 返回普通值:如果回调函数返回一个普通值(如数字、字符串、对象),该值会被自动包装成一个 Fulfilled 状态的 Promise,并传递给下一个
.then()。 - 返回 Promise:如果回调函数返回一个新的 Promise(例如发起了一个新的网络请求),当前的链会暂停,直到这个新的 Promise 状态变为 Fulfilled,其结果才会传递给下一个
.then()。 - 抛出错误:如果在回调函数中使用
throw关键字抛出错误,或者代码运行时报错,链的状态会立即变为 Rejected,跳过后续所有的.then(),直接寻找最近的.catch()。
下表总结了 .then() 回调函数中不同操作对链的影响:
| 操作类型 | 返回内容 | 下一个 .then() 接收到的值 | 链的状态流转 |
|---|---|---|---|
| 返回普通值 | 100, 'abc', {obj} |
该普通值本身 | 变为 Fulfilled (成功) |
| 返回 Promise | new Promise(...) |
该 Promise resolve 的结果 | 等待该 Promise 结束 |
| 无 return (隐式) | undefined |
undefined |
变为 Fulfilled (成功) |
| 抛出错误 | throw new Error() |
不传递 (跳过) | 变为 Rejected (失败) |
3. 使用 catch() 捕获与错误恢复
.catch() 是专门用于处理 Rejected 状态的方法。它在链中的位置非常灵活,但遵循特定的“冒泡”规则。
- 放置
.catch()在链的末尾。它会捕获链中前面任何一个.then()里发生的错误。 - 理解错误冒泡:如果错误发生在第 1 个
.then(),且该.then()后面没有紧跟.catch(),错误会跳过第 2 个、第 3 个.then(),直到遇见第一个.catch()。
为了直观展示这一过程,请参考下方的状态流转图:
graph LR
A["Start: 初始 Promise"] --> B["then: 第一步处理"]
B -- 返回值或正常结束 --> C["then: 第二步处理"]
B -- 抛出错误 throw --> D["catch: 捕获错误"]
C -- 继续执行 --> E["then: 后续步骤"]
C -- 抛出错误 throw --> D
D -- 返回恢复值 --> F["then: 错误后的后续处理"]
D -- 重新抛出错误 --> G["Uncaught (in promise)"]
代码示例:错误冒泡
Promise.resolve()
.then(() => {
console.log('Step 1');
throw new Error('Something went wrong');
})
.then(() => {
console.log('Step 2'); // 这行代码永远不会执行
})
.catch((err) => {
console.error('Caught:', err.message); // 输出: Caught: Something went wrong
});
4. 实现错误的“吞没”与恢复
.catch() 不仅可以记录错误,还可以返回一个值,从而让链“恢复”到 Fulfilled 状态,继续执行后续的 .then()。这被称为错误恢复。
- 在
.catch()中 返回一个默认值或备用数据。 - 验证后续的
.then()是否能正常接收到这个返回值并继续执行。
代码示例:错误恢复
fetchUserData()
.then(user => {
if (!user) {
throw new Error('User not found');
}
return user.profile;
})
.catch(err => {
console.warn(err.message); // 输出警告
// 返回一个默认的空对象,防止程序崩溃
return { name: 'Guest', role: 'visitor' };
})
.then(profile => {
console.log(`Welcome, ${profile.name}`); // 输出: Welcome, Guest
// 继续执行后续逻辑
});
```
注意:如果在 `.catch()` 中再次 `throw` 一个错误,或者返回一个 Rejected 状态的 Promise,链会再次进入错误状态,需要后续的 `.catch()` 来接住。
---
### 5. 避免嵌套,保持链式扁平
最常见的错误是在 `.then()` 内部再次使用 `.then()` 而不进行 `return`,这会导致代码缩进混乱,形成嵌套结构。
1. **检查**代码是否存在多层缩进的 `.then()`。
2. **提取**内部的 Promise,并在外层直接 `return` 它。
3. **确保**所有的后续处理都保持在同一级缩进上。
**反模式(错误示范):**
```javascript
promise
.then(result => {
fetchAnother(result)
.then(newResult => {
console.log(newResult); // 嵌套导致难以管理
});
});
```
**正模式(正确示范):**
```javascript
promise
.then(result => {
return fetchAnother(result); // 直接返回 Promise
})
.then(newResult => {
console.log(newResult); // 扁平化结构
});
```
---
### 6. 链式调用的完整流程演练
结合以上所有知识点,我们来构建一个完整的模拟场景:获取用户 ID,获取用户详情,如果获取失败则使用默认访客数据,最后保存日志。
1. **定义**模拟的异步函数。
2. **串联** `getUserId`, `getUserInfo`, 和 `saveLog`。
3. **使用** `.catch()` 处理中间可能出现的异常。
```javascript
// 模拟异步函数
function getUserId() {
return Promise.resolve(101);
}
function getUserInfo(id) {
if (id > 1000) {
return Promise.resolve({ id: id, name: 'Admin' });
}
// 模拟失败
return Promise.reject(new Error('ID too small'));
}
function saveLog(data) {
console.log('Log saved:', data);
return Promise.resolve('Success');
}
// 执行链式调用
getUserId()
.then(id => {
console.log(`Got ID: ${id}`);
return getUserInfo(id); // 返回 Promise
})
.then(user => {
console.log('Got User:', user);
return user;
})
.catch(err => {
console.error('Error handled:', err.message);
// 恢复策略:返回一个默认用户对象
return { id: 0, name: 'Default User', source: 'fallback' };
})
.then(finalUser => {
console.log('Final user object:', finalUser);
return saveLog(finalUser);
})
.then(status => {
console.log('Final status:', status);
});
在上述代码中,getUserInfo 返回了一个 Rejected 状态的 Promise。链式调用跳过了随后的 .then()(如果有未处理的错误),直接进入了 .catch()。在 .catch() 中我们返回了一个默认对象,这使得链“复活”,最后一个 .then() 成功接收到了数据并执行了保存日志的操作。

暂无评论,快来抢沙发吧!