Fetch API 是浏览器原生提供的用于发起 HTTP 请求的接口,它基于 Promise 设计,相比传统的 XMLHttpRequest,代码更简洁,也更容易和 async/await 配合使用。核心方法是 fetch(),它接收一个 URL 和一个可选的配置对象,返回一个 Promise,该 Promise 会解析为 Response 对象。借助 Response 对象上的 status、ok 属性以及 json()、text() 等方法,开发者可以方便地读取响应内容和状态信息。

Fetch的基本语法与响应对象
调用 fetch() 的最简单形式是只传入一个 URL,此时默认发起 GET 请求。fetch() 返回的 Promise 在请求完成(无论成功或失败)后解析为 Response 对象,但需要注意一个关键点:只要网络层面没有发生错误,即使服务器返回 404 或 500 状态码,Promise 依然会正常 resolve,而不会 reject。这与很多人的直觉不同,也是实际开发中容易漏掉错误处理的根源。
下面是一个最基础的使用示例,展示如何获取数据并解析为 JSON:
fetch('/api/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('请求失败:', error));
使用 async/await 重写上面的逻辑,代码结构会更加清晰。await fetch() 会直接得到 Response 对象,然后调用 response.json() 得到数据。但要注意,response.json() 本身也是一个异步操作,需要再次 await。如果响应不是合法的 JSON 格式,json() 方法会抛出一个 SyntaxError,需要在 catch 中处理。
Response 对象还提供了 text()、blob()、arrayBuffer()、formData() 等读取方法,分别对应文本、二进制文件、原始缓冲区和表单数据。每种方法都只能调用一次,因为响应体是流式的,读取后即被消费。如果需要同时保留原始文本和解析后的对象,可以先调用 text() 再手动 JSON.parse,但这样会多一次内存拷贝,在数据量较大时需要权衡。
发送GET请求与查询参数构造
GET 请求通常用于获取数据,参数一般拼接在 URL 后面。手动拼接字符串容易忘记 encodeURIComponent 导致特殊字符出错,例如用户输入了 & 或 = 符号,直接拼入 URL 会被误解析为参数分隔符。推荐使用 URLSearchParams 对象来构造查询字符串,它会自动处理编码问题。
下面的代码演示了如何构造带参数的 GET 请求:
const params = new URLSearchParams({
keyword: '前端开发',
page: 1,
size: 20
});
const url = `/api/search?${params.toString()}`;
fetch(url)
.then(response => response.json())
.then(data => console.log(data));
如果 URL 本身已经包含部分查询参数,还可以通过 new URL() 解析后修改 searchParams 属性,这样能避免重复的问号或与号。另外,GET 请求默认不需要设置 Content-Type 请求头,因为 GET 请求没有请求体。如果需要携带认证信息,可以在 headers 中设置 Authorization 或者使用 credentials 选项控制 Cookie 的发送行为。
还有一个细节是缓存。fetch 默认遵循 HTTP 缓存机制,但如果想强制刷新,可以在配置对象中设置 cache: 'no-cache' 或 cache: 'reload'。对于实时性要求高的接口,设置合适的缓存策略可以减少不必要的网络请求。
发送POST请求与请求头配置
POST 请求用来提交数据给服务器,常见的数据格式有 JSON、表单编码和 FormData。对于 JSON 数据,需要手动设置 Content-Type 为 application/json,并使用 JSON.stringify 将对象转为字符串。如果不设置 Content-Type,服务器可能无法正确解析请求体。
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ name: '张三', age: 28 })
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP 错误,状态码:${response.status}`);
}
return response.json();
})
.then(data => console.log('创建成功:', data))
.catch(error => console.error('请求失败:', error));
上传文件时使用 FormData 对象,将文件或表单字段追加进去,然后直接作为 body 传入。此时不需要手动设置 Content-Type,浏览器会自动生成带有 boundary 的 multipart/form-data 请求头,手动设置反而会导致 boundary 丢失。
const formData = new FormData();
formData.append('avatar', fileInput.files[0]);
formData.append('username', '李四');
fetch('/api/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => console.log('上传结果:', data));
跨域请求时,如果服务器没有正确配置 CORS 响应头,浏览器会阻止读取响应。对于需要携带 Cookie 的跨域请求,必须同时设置 credentials: 'include',并且服务器返回的 Access-Control-Allow-Origin 不能是通配符 *,必须指定具体域名。此外,一些自定义请求头会触发预检请求(OPTIONS),服务器需要正确处理 OPTIONS 方法才能完成后续请求。
错误处理与超时控制
前面提到,fetch 只有在网络故障(如 DNS 解析失败、连接被拒绝、跨域被浏览器拦截)时才会 reject,而对于 404、500 等 HTTP 错误状态,Promise 会正常 resolve。因此必须在 then 回调中检查 response.ok 或 response.status 来判断请求是否真正成功。常见的做法是封装一个判断函数,将非 2xx 状态码统一抛出异常。
下面展示一个带有完整错误处理的封装示例:
async function request(url, options = {}) {
const response = await fetch(url, options);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`请求失败,状态码 ${response.status},信息:${errorText}`);
}
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
}
Fetch 本身不支持 timeout 参数,不像 XHR 有 timeout 属性。要实现超时控制,需要结合 AbortController 和 setTimeout。当超时时间到达时调用 controller.abort(),fetch 会 reject 一个名为 AbortError 的异常。下面的代码演示了如何给请求加上 5 秒超时限制:
function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
return fetch(url, { ...options, signal: controller.signal })
.finally(() => clearTimeout(timer));
}
fetchWithTimeout('/api/slow-task', {}, 5000)
.then(response => response.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.error('请求超时被取消');
} else {
console.error('请求失败:', err);
}
});
需要注意的是,abort() 触发后,如果服务器已经收到请求,可能无法真正中止服务器端的处理,只能停止客户端等待。对于需要严格控制资源的场景,应该让服务器端支持幂等操作或通过取消令牌等方式配合。
取消请求与并发控制
AbortController 不仅可以用于超时,也可以用于用户主动取消请求,比如在搜索输入框中停止上一次未完成的请求、切换页面时中断正在进行的下载等。每个 fetch 请求都需要一个独立的 signal,否则多个请求会同时被取消。一个 controller 只能被 abort 一次,如果需要复用,需要重新创建新的 AbortController 实例。
let currentController = null;
async function search(keyword) {
if (currentController) {
currentController.abort();
}
currentController = new AbortController();
const signal = currentController.signal;
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(keyword)}`, { signal });
const data = await response.json();
renderResults(data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('上一次搜索已取消');
} else {
console.error('搜索失败:', err);
}
}
}
在并发请求场景中,Promise.all 会在任意一个 Promise reject 时立即失败,而 Promise.allSettled 会等待所有 Promise 完成,不论成功或失败。如果需要对多个请求统一设置取消,可以让它们共享同一个 signal,这样任意一个触发 abort 或外部调用 abort 都会取消全部请求。另外,对于大量并发请求,建议控制并发数量,避免浏览器连接数上限导致请求排队。
与XMLHttpRequest的对比及封装建议
相比 XMLHttpRequest,Fetch API 在代码简洁性、Promise 原生支持、Response 对象设计上都有明显优势。XHR 需要监听 onreadystatechange 事件,手动判断 readyState 和 status,代码较为冗余。而 Fetch 的错误处理虽然需要额外判断 response.ok,但整体逻辑更加线性。不过 XHR 也有 Fetch 不具备的能力,比如上传进度事件 onprogress、下载进度事件,以及 abort 后可以继续复用对象等。如果项目中需要展示上传进度条,目前 Fetch 还没有原生支持,可能需要借助 service worker 或流式读取,实现起来比较复杂。
下面是一个 XHR 的典型写法,用来和 Fetch 做对照:
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(JSON.parse(xhr.responseText));
} else {
console.error('请求失败,状态码:' + xhr.status);
}
}
};
xhr.onerror = function() {
console.error('网络错误');
};
xhr.send();
在实际项目中,直接使用原生 Fetch 可能会遇到重复代码较多的问题,建议封装一个统一的请求模块,集中处理 baseURL、认证头、超时、错误提示、重试逻辑等。如果项目已经使用 Vue、React 等框架,可以结合框架的拦截器机制。对于需要上传进度、取消令牌、请求重试等高级功能的场景,axios 等第三方库仍然是更省心的选择,它们内部基于 XHR 或 Fetch 实现,但提供了更完善的接口和拦截器体系。理解 Fetch 的底层机制,有助于在需要时自行实现轻量封装,减少对库的依赖。
Fetch APIJavaScript异步请求修改时间:2026-09-25 09:51:39