广告

前端必读:Promise.then 的用法详解与实战技巧

1. Promise.then 的基本用法与工作机制

Promise.then 的基本语义

then 是 Promise 的实例方法,用于在 Promise 解决(resolve)或拒绝(reject)后执行对应的回调。它接收两个可选参数:onFulfilledonRejected,分别处理成功和失败的分支。

执行时,回调 不会立即执行,而是在当前任务队列的微任务阶段进入队列,待 Promise 状态确定后按顺序执行。返回值是一个新的 Promise,允许进行链式操作。

需要特别注意的是,如果 onFulfilledonRejected 中抛出异常,返回的 Promise 将被拒绝,并传递该异常。

// 基本用法
fetch('/api/data').then(response => response.json()) // 成功回调.then(data => console.log(data))  // 继续链式调用.catch(err => console.error(err)); // 错误处理

链式调用与返回值的影响

then 的回调中返回的值会被下一个 then 捕获;如果返回的是一个 Promise,下一步会等待该 Promise 的状态变更,从而实现异步顺序控制。

链式调用 的核心特性是连续使用 then,形成流水线式的异步处理。

前端必读:Promise.then 的用法详解与实战技巧

下面的示例展示返回数值 vs 返回 Promise 的影响:

// 返回数值,后续 then 接收该值
Promise.resolve(1).then(v => v + 1).then(v => console.log(v)); // 2// 返回 Promise,后续 then 等待该 Promise
Promise.resolve(1).then(v => Promise.resolve(v + 1)).then(v => console.log(v)); // 2

常见错误场景与解决策略

在使用 then 时,常见错误包括:忘记返回一个 Promise、在回调中直接抛出异常、以及错误没有向上抛至链头的 catch

解决策略包括:始终返回 一个 Promise 或值,避免直接抛错,并在链的末端显式使用 catch 或第三方错误处理方案。

错误示例与正确用法对比:

// 错误示例:不返回 Promise
fetch('/api').then(data => { data.json(); }) // 未返回值// 正确示例:返回 Promise
fetch('/api').then(data => data.json()).then(json => console.log(json));

2. Promise.then 的实战技巧与常见组合

错误处理策略:catch 与 第一个完成的处理

在实际场景中,全局错误处理 常用做法是在链的末端添加 catch,以捕获所有未处理的错误。也可以在链的某些阶段为 onRejected 提供单独的处理函数。

catch 只捕获来自上游链路的错误;如果在 catch 内重新抛错,链条会继续向上传递该错误。

全局错误处理的典型用法:

// 全局错误捕获
fetch('/api').then(res => res.json()).then(data => process(data)).catch(err => console.error('请求失败:', err));

与 Promise.all、Promise.race 的协作

在并发场景中,Promise.all 可以将多个 Promise 聚合成一个,便于在 then 中统一处理结果。若任意一个 Promise 失败,整个组会进入 reject 路径。

如果使用 Promise.race,则谁先完成就触发对应的 then,这是实现超时、优先选择的常用技巧。

示例:

// Promise.all 示例
const requests = [fetch('/a'), fetch('/b'), fetch('/c')];
Promise.all(requests).then(results => Promise.all(results.map(r => r.json()))).then(allJson => console.log(allJson)).catch(err => console.error('其中一个请求失败:', err));// Promise.race 示例
const p1 = new Promise(resolve => setTimeout(() => resolve('slow'), 3000));
const p2 = new Promise(resolve => setTimeout(() => resolve('fast'), 1000));
Promise.race([p1, p2]).then(v => console.log(v));

异步流程中的顺序控制与微任务队列

在事件循环模型中,Promise.then 的回调进入微任务队列,保证在同一轮事件循环中尽快执行。但如果大量微任务堆积,也可能导致界面卡顿。

理解 同步边界异步边界,可以帮助设计更平滑的 UI 流程,避免在渲染阶段重复触发微任务。

微任务队列的执行顺序示例:

// 微任务示例:顺序执行
console.log('start');
Promise.resolve().then(() => console.log('microtask 1'));
Promise.resolve().then(() => console.log('microtask 2'));
console.log('end'); // 先输出 start, end, microtask 1, microtask 2

3. 实战示例:从 API 调用到页面渲染

获取多资源并按顺序渲染

本节演示如何通过 Promise.then 链结合 fetch 获取多资源,并按顺序将数据渲染到页面。通过串联 then,可以把每一步的结果传递下去。

通过将数据处理和 DOM 更新分离成独立阶段,可读性与可维护性 得到提升。记得在末端保持清晰的错误处理。

// 获取多资源并按顺序渲染
fetch('/api/first').then(res => res.json()).then(first => {document.getElementById('first').textContent = first.name;return fetch('/api/second');}).then(res => res.json()).then(second => {document.getElementById('second').textContent = second.title;}).catch(err => console.error('渲染失败:', err));

异常兜底与回退数据

在网络波动或接口不可用时,提供一个 兜底数据 可以提升用户体验。可以在 catch 处设定回退方案。

通过在失败时切换到回退数据,仍然保持页面的可用性与连贯性。

// 异常兜底示例
fetch('/api/data').then(res => res.ok ? res.json() : Promise.reject('请求错误')).then(data => {render(data);}).catch(err => {renderFallback();console.warn('使用回退数据:', err);});

广告