文章目录

JavaScript Promise:then() 与 catch() 链式调用

发布于 2026-04-17 08:26:49 · 浏览 18 次 · 评论 0 条

JavaScript Promise:then() 与 catch() 链式调用

JavaScript 中的 Promise 链式调用是处理异步操作的核心机制。通过 then()catch() 的串联,可以将复杂的异步逻辑转化为线性的、同步感极强的代码结构。掌握链式调用的数据流转与错误冒泡机制,是编写健壮异步代码的关键。


1. 理解链式调用的基础机制

Promise 链式调用的核心在于:每一个 then() 方法或 catch() 方法在执行后,都会返回一个新的 Promise 对象。这使得我们可以无限地串联后续操作,而不会陷入回调地狱。

  1. 创建一个基础的 Promise 对象。
  2. 调用第一个 .then() 方法并传入回调函数。
  3. 观察返回值:无论回调函数内部执行了什么操作,.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() 接收到的参数。理解这一规则是控制数据流向的关键。

  1. 返回普通值:如果回调函数返回一个普通值(如数字、字符串、对象),该值会被自动包装成一个 Fulfilled 状态的 Promise,并传递给下一个 .then()
  2. 返回 Promise:如果回调函数返回一个新的 Promise(例如发起了一个新的网络请求),当前的链会暂停,直到这个新的 Promise 状态变为 Fulfilled,其结果才会传递给下一个 .then()
  3. 抛出错误:如果在回调函数中使用 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 状态的方法。它在链中的位置非常灵活,但遵循特定的“冒泡”规则。

  1. 放置 .catch() 在链的末尾。它会捕获链中前面任何一个 .then() 里发生的错误。
  2. 理解错误冒泡:如果错误发生在第 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()。这被称为错误恢复。

  1. .catch()返回一个默认值或备用数据。
  2. 验证后续的 .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() 成功接收到了数据并执行了保存日志的操作。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文