在前端异步编程领域,Promise 是解决回调地狱的核心方案,而 fetch 作为现代浏览器原生支持的网络请求 API,其设计本身就深度依赖 Promise。掌握二者的结合使用,不仅是日常开发的基础,更是前端面试的高频考点。本文将从核心概念联动、实战场景应用、面试重点拆解三个维度,帮你彻底搞懂 fetch 与 Promise 的协同逻辑。
一、核心概念:为什么 fetch 天然适配 Promise?
在讲解结合用法前,我们先明确两个核心概念的关联:
1.1 Promise 的核心价值
Promise 是一种用于处理异步操作的对象,它将异步操作的结果(成功/失败)封装为可预测的状态流转,避免了多层嵌套的回调地狱。其核心特性包括:
- 三种状态:
pending(进行中)、fulfilled(成功)、rejected(失败),状态一旦改变不可逆; - 链式调用:通过
then()接收成功结果,catch()捕获错误,支持链式串联多个异步操作; - 异步穿透:允许在链式调用中跳过部分
then(),直接由后续catch()捕获错误。
1.2 fetch 的设计本质
fetch 是浏览器提供的用于替代 XMLHttpRequest 的网络请求 API,其核心设计理念就是"基于 Promise 封装异步请求"。与 XHR 不同,fetch 调用后会直接返回一个 Promise 对象:
- 当请求发出后,Promise 处于
pending状态; - 当服务器返回响应(无论 HTTP 状态码是否为 2xx),Promise 会变为
fulfilled,并将响应对象(Response)传递给then(); - 只有当网络故障(如断网、跨域未授权等)导致请求无法发出时,Promise 才会变为
rejected,错误会被catch()捕获。
注意:fetch 的 Promise 不会因 HTTP 错误状态码(如 404、500)而 reject,这是面试高频易错点!
二、实战场景:fetch 与 Promise 结合用法全解析
基于二者的天然适配性,实际开发中我们通过 Promise 的链式调用,就能优雅地处理 fetch 的请求、响应解析、错误捕获全流程。以下是高频实战场景:
2.1 基础场景:GET 请求与响应解析
fetch 默认发起 GET 请求,需通过 Response 对象的方法(如 json()、text())解析响应体,而这些方法本身也返回 Promise,因此需要嵌套一层 then():
javascript
// 基础 GET 请求
fetch('https://api.example.com/data')
// 第一次 then:接收响应对象,解析为 JSON(返回 Promise)
.then(response => {
// 手动处理 HTTP 错误状态码
if (!response.ok) {
throw new Error(`HTTP 错误:${response.status}`);
}
return response.json(); // 解析 JSON 格式响应体,返回 Promise
})
// 第二次 then:接收解析后的业务数据
.then(data => {
console.log('请求成功:', data);
})
// catch:捕获所有错误(网络错误 + 手动抛出的 HTTP 错误)
.catch(error => {
console.error('请求失败:', error);
});
核心逻辑:外层 Promise 处理请求发起与响应接收,内层 Promise 处理响应体解析,通过链式调用串联,代码清晰无嵌套。
2.2 进阶场景:POST 请求与请求配置
发起 POST 请求时,需通过 fetch 的第二个参数配置请求方法、请求头、请求体等,结合 Promise 处理复杂异步逻辑:
javascript
// 定义请求数据
const postData = { username: 'test', password: '123456' };
// POST 请求
fetch('https://api.example.com/login', {
method: 'POST', // 请求方法
headers: {
'Content-Type': 'application/json', // 声明请求体格式为 JSON
},
body: JSON.stringify(postData), // 序列化请求体(必须为字符串)
})
.then(response => {//这里是处理fetch返回的promise对象
if (!response.ok) throw new Error(`状态码:${response.status}`);
return response.json();
})
.then(result => {//response.json也是一个promise对象
if (result.code === 200) {
console.log('登录成功:', result.data.token);
// 可继续发起后续请求(如获取用户信息),形成 Promise 链式串联
return fetch('https://api.example.com/userInfo', {
headers: { Authorization: `Bearer ${result.data.token}` }
});
} else {
throw new Error(result.msg);
}
})
// 这里是获取用户对象fetch 返回的promise
.then(userRes => userRes.json())
// 处理获取用户信息接口返回的数据
.then(userData => console.log('用户信息:', userData))
.catch(error => console.error('流程失败:', error));
关键亮点:在一个 Promise 链中串联多个异步请求(登录 → 获取用户信息),前一个请求的结果作为后一个请求的参数,实现异步流程的线性化。
2.3 高级场景:Promise 工具函数结合 fetch
利用 Promise 的工具函数(如 Promise.all()、Promise.race()),可实现 fetch 的批量请求或超时控制:
javascript
// 1. Promise.all():并行发起多个请求,全部成功才返回结果
const request1 = fetch('https://api.example.com/data1');
const request2 = fetch('https://api.example.com/data2');
Promise.all([request1, request2])
.then(responses => Promise.all(responses.map(res => res.json())))
.then([data1, data2] => {
console.log('批量请求成功:', data1, data2);
})
.catch(error => console.error('任意一个请求失败:', error));
// 2. Promise.race():超时控制(请求超时时中断并抛出错误)
const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('请求超时(3秒)')), 3000);
});
Promise.race([fetch('https://api.example.com/slowData'), timeoutPromise])
.then(res => res.json())
.then(data => console.log('请求成功:', data))
.catch(error => console.error('请求失败:', error));
2.4 优化场景:Promise 工具函数结合async,await
利用async,await将请求更加优雅,统一处理error
javascript
const search = async() =>{
try{
const res = await fetch('/api/test')
const data = res.json()
console.log(data,'返回接口数据')
}catch(err){
console.log('请求出错',err)
}
}
三、面试重点:高频问题与核心答案
fetch 与 Promise 的结合是前端面试的核心考点,以下是高频问题及精准回答思路:
3.1 问题 1:fetch 的返回值是什么?它的 Promise 什么时候会 reject?
- fetch 的返回值是一个 Promise 对象;
- 该 Promise 仅在「网络故障」时才会 reject(如断网、跨域未配置 CORS、域名无法解析等);
- 即使服务器返回 404、500 等 HTTP 错误状态码,Promise 仍会 resolve,此时需要通过 Response 对象的
ok属性(true 表示 2xx 状态码)手动判断请求是否成功,并抛出错误。
3.2 问题 2:如何用 Promise 处理 fetch 的错误(包括网络错误和 HTTP 错误)?
核心答案:通过「手动判断 HTTP 状态码 + catch 捕获」的组合实现全量错误处理:
javascript
fetch('/api/data')
.then(res => {
// 第一步:判断 HTTP 状态码,非 2xx 则抛出错误
if (!res.ok) throw new Error(`HTTP 错误:${res.status}`);
return res.json();
})
.then(data => console.log('成功', data))
.catch(err => {
// 捕获两类错误:网络错误 + 手动抛出的 HTTP 错误
console.error('失败', err);
});
3.3 问题 3:fetch 相比 XMLHttpRequest,结合 Promise 有哪些优势?
- 代码更简洁:Promise 链式调用替代 XHR 的多层回调,避免回调地狱;
- 状态更可控:Promise 的状态不可逆特性,让异步流程更可预测;
- 原生适配:fetch 天生返回 Promise,无需手动封装,而 XHR 需要手动用 Promise 包裹才能实现链式调用;
- 功能更强大:支持 Promise 工具函数(如 Promise.all())实现批量请求,轻松处理复杂异步场景。
3.4 问题 4:如何用 Promise.race() 给 fetch 设置超时时间?
创建一个超时 Promise(指定时间后 reject),与 fetch 的 Promise 进行 race 竞争,谁先改变状态就以谁的结果为准:
javascript
function fetchWithTimeout(url, timeout = 3000) {
// 超时 Promise
const timeoutTask = new Promise((_, reject) => {
setTimeout(() => reject(new Error(`超时 ${timeout}ms`)), timeout);
});
// 竞争:fetch 成功/失败 或 超时
return Promise.race([fetch(url), timeoutTask]);
}
// 使用
fetchWithTimeout('/api/slowData')
.then(res => res.json())
.then(data => console.log('成功', data))
.catch(err => console.error('失败', err));
3.5 问题 5:fetch 中如何实现请求中断?结合 Promise 怎么处理?
fetch 本身不支持中断,但可通过 AbortController 与 Promise 结合实现:
ini
// 1. 创建 AbortController 实例
const controller = new AbortController();
// 2. 获取信号对象
const signal = controller.signal;
// 3. 发起 fetch 请求,将 signal 传入配置
fetch('/api/data', { signal })
.then(res => res.json())
.then(data => console.log('成功', data))
.catch(err => {
// 4. 中断时会触发 AbortError
if (err.name === 'AbortError') {
console.log('请求已中断');
return;
}
console.error('其他错误', err);
});
// 5. 主动中断请求(如用户点击取消按钮)
controller.abort();
原理:AbortController 的 signal 对象与 fetch 关联,调用 abort() 时,signal 会触发 abort 事件,fetch 会立即 reject 并抛出 AbortError。
四、总结
fetch 与 Promise 的结合,核心是利用 Promise 的状态管理和链式调用特性,解决 fetch 异步请求的流程控制问题。日常开发中,需重点掌握响应解析、错误处理(尤其是 HTTP 错误)、批量请求、超时控制等场景;面试中,要牢记 fetch 的 Promise 状态规则、错误处理逻辑、与 XHR 的差异及请求中断方案等核心考点。
掌握二者的协同逻辑,不仅能提升异步代码的可读性和可维护性,更能在面试中快速精准地应对高频问题。建议结合实际场景多写多练,加深对 Promise 异步流转和 fetch 核心特性的理解。