用JavaScript fetch API发送异步请求的代码模板
提供结构规范、开箱即用的 JavaScript fetch API 异步请求代码模板。涵盖基础 GET、JSON POST、响应状态判定、超时中断与工程化封装,帮助开发者快速掌握安全健壮的前端网络请求写法。
在现代 Web 开发中,原生 fetch API 已经全面取代传统的 XMLHttpRequest,成为浏览器端发送 HTTP 异步请求的标准方式。虽然 fetch 的语法直观简洁,但由于其内部对 HTTP 错误状态码的特殊处理机制,很多初学者在编写请求代码时容易漏掉状态校验、超时保护与响应流控制。本文直接提供标准化、经过工程验证的 fetch 异步请求代码模板,涵盖基础 GET、数据 POST、网络及业务异常双层拦截,以及通用的模块化封装函数。
一、最简 GET 请求代码模板:标准 async/await 结构
GET 请求用于从服务器获取数据,也是最常用的请求方式。在 fetch 中,默认的请求方法就是 GET,因此在没有额外配置的情况下可以省略 method: 'GET'。但在生产实践中,必须通过 try...catch 结合 async/await 构建可控的异常捕获链路。
以下是用于获取 JSON 数据的标准 GET 请求模板:
/**
* 发送 GET 请求的标准异步模板
* @param {string} url 请求的目标地址
* @param {Record<string, any>} [params] 可选的查询参数对象
* @returns {Promise<any>} 解析后的 JSON 数据
*/
async function fetchGetData(url, params = {}) {
try {
// 1. 如果存在查询参数,使用 URLSearchParams 格式化并拼接到 URL
const targetUrl = new URL(url, window.location.origin);
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null) {
targetUrl.searchParams.append(key, params[key]);
}
});
// 2. 发起网络请求
const response = await fetch(targetUrl.toString(), {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
// 3. 校验 HTTP 响应状态码是否在 200~299 区间
if (!response.ok) {
throw new Error(`HTTP 状态异常: ${response.status} ${response.statusText}`);
}
// 4. 将响应流解析为 JSON 对象
const result = await response.json();
return result;
} catch (error) {
// 5. 捕获网络异常、DNS 解析失败或主动抛出的 HTTP 错误
console.error('GET 请求失败:', error.message);
throw error;
}
}
// 调用示例:
// fetchGetData('/api/articles', { category: 'javascript', page: 1 })
// .then(data => console.log('文章列表:', data))
// .catch(err => console.error('业务捕获到错误:', err));
body。如果在调用 fetch 时传入包含 body 的 GET 配置,现代浏览器会直接抛出 TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body 异常。
二、提交数据:POST 请求配置模板与 Headers 设定
向服务端提交数据时,最常用的是通过 POST 传递 JSON 格式载荷。与 GET 请求相比,POST 模板的核心在于两点:一是必须在 headers 中指明 'Content-Type': 'application/json',否则服务端可能将其当做纯文本或二进制流而无法正常反序列化;二是在 body 中使用 JSON.stringify() 将 JavaScript 对象序列化为字符串。
/**
* 发送 POST 请求提交 JSON 数据的标准模板
* @param {string} url 目标地址
* @param {object} payload 待发送的业务数据对象
* @returns {Promise<any>} 服务端返回的数据
*/
async function fetchPostJson(url, payload = {}) {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(payload)
});
// 判定服务端是否成功返回 2xx 状态码
if (!response.ok) {
// 尝试读取服务端返回的具体错误说明(如验证未通过的字段提示)
let errorMessage = `请求失败: ${response.status}`;
try {
const errorBody = await response.json();
errorMessage = errorBody.message || errorMessage;
} catch (_) {
// 如果返回的不是 JSON,退回默认状态文本
errorMessage = response.statusText || errorMessage;
}
throw new Error(errorMessage);
}
return await response.json();
} catch (error) {
console.error('POST 请求提交失败:', error);
throw error;
}
}
// 调用示例:
// fetchPostJson('/api/users/profile', { nickname: '张三', role: 'admin' })
// .then(res => console.log('保存成功:', res))
// .catch(err => alert('保存失败: ' + err.message));
拓展场景:发送 FormData 表单数据
如果提交的内容包含文件上传(如 <input type="file">)或标准表单对象,请直接使用浏览器原生的 FormData 对象。切记此时不要手动设置 Content-Type,浏览器会自动添加形如 multipart/form-data; boundary=----WebKitFormBoundary... 的分界标识符,若手动设置反而会丢失 boundary 导致服务端解析失败。
async function fetchPostFormData(url, formData) {
const response = await fetch(url, {
method: 'POST',
// 关键:不要配置 'Content-Type',让浏览器自动计算 Boundary
body: formData
});
if (!response.ok) throw new Error(`上传失败: ${response.status}`);
return await response.json();
}
三、核心陷阱排查:为什么 fetch 不会自动 reject HTTP 错误?
这是从 axios 或传统请求库切换到原生 fetch 时最容易踩坑的技术细节:当服务器返回 404 Not Found、500 Internal Server Error 或 403 Forbidden 等状态码时,fetch() 返回的 Promise 并不会被 reject,而是会正常 resolve!
在 fetch 的设计规范中,只有当遇到网络层致命故障(例如用户断网、DNS 查询失败、网络连接被重置,或者触发跨域 CORS 拦截策略)导致根本无法完成 HTTP 会话时,Promise 才会进入 reject 状态并跳入 catch 块。
fetch 获取到 response 对象后,执行业务逻辑之前,必须首先检查 response.ok。该布尔值属性在状态码位于 200 ~ 299 时为 true,其余均为 false。只有在 !response.ok 时手动执行 throw new Error(...),才能让错误顺利流转到统一的 catch 异常处理块中。
| 响应表现 / 状态码 | Promise 状态 | 是否进入 catch 块 | 推荐处理方式 |
|---|---|---|---|
| 200 OK / 201 Created | Resolved | 否 | response.ok === true,调用 response.json() 消费数据 |
| 400 / 401 / 404 / 500 | Resolved(注意!) | 否(若不手动 throw) | 检查 if (!response.ok),主动提取服务端错误体并 throw |
| 网络离线 / DNS 解析失败 | Rejected | 是 | 跳入 catch (err),类型为 TypeError: Failed to fetch |
| 请求超时 / 主动 Abort | Rejected | 是 | 跳入 catch (err),名称为 AbortError 或 TimeoutError |
四、超时控制与请求中断:基于 AbortController 的健壮模板
原生 fetch API 本身并不像传统的 XHR 那样提供 timeout 配置项。如果后端接口卡死或处于弱网慢速连接,未配置超时的 fetch 会一直处于挂起状态,耗尽浏览器并发连接池资源并严重影响用户体验。
现代标准中,通过原生的 AbortController(或更高版本直接支持的 AbortSignal.timeout(ms))可以为请求精准配置超时熔断与主动取消机制。
/**
* 带有超时控制的 fetch 请求模板
* @param {string} url 请求地址
* @param {RequestInit} [options] 配置选项
* @param {number} [timeoutMs] 超时毫秒数,默认 8000ms
*/
async function fetchWithTimeout(url, options = {}, timeoutMs = 8000) {
// 1. 初始化中断控制器
const controller = new AbortController();
const { signal } = controller;
// 2. 设置定时器:达到超时时间后触发中断
const timeoutId = setTimeout(() => {
controller.abort(new Error(`请求超时,超过了设定的 ${timeoutMs}ms 限制`));
}, timeoutMs);
try {
// 3. 将 signal 信号传递给 fetch options
const response = await fetch(url, {
...options,
signal
});
// 4. 及时清除超时定时器,防止成功返回后仍然触发 abort
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP 异常: ${response.status}`);
}
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
// 5. 识别是否由 AbortController 中断引起
if (error.name === 'AbortError') {
console.warn('请求已由控制器主动取消或已触发超时');
} else {
console.error('网络或业务异常:', error.message);
}
throw error;
}
}
signal: AbortSignal.timeout(8000),无需手动声明 setTimeout 与 clearTimeout。但在需要向下兼容或在组件卸载时手动触发取消的场景下,上述 new AbortController() 模板依旧是最稳妥通用的方案。
五、生产级通用封装:开箱即用的 request 请求函数模板
在实际项目开发中,如果每个接口都手写一遍 fetch、Headers 拼接、状态检查和超时定时器,代码将充斥大量冗余。下面提供一套适用于中小型工程的轻量级通用请求客户端模板。该模板支持自动拼装 Base URL、自动添加鉴权 Token、统一序列化、超时防护与标准化错误抛出。
/**
* 工业级通用异步请求封装模板 (http-client.js)
*/
const DEFAULT_CONFIG = {
baseURL: 'https://api.example.com/v1',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
/**
* 核心统一请求方法
* @param {string} endpoint 接口相对路径或绝对地址
* @param {object} options 请求配置
*/
async function request(endpoint, options = {}) {
const {
method = 'GET',
params,
body,
headers = {},
timeout = DEFAULT_CONFIG.timeout,
...customOptions
} = options;
// 1. 拼接完整 URL
let fullUrl = endpoint.startsWith('http')
? endpoint
: `${DEFAULT_CONFIG.baseURL.replace(/\/+$/, '')}/${endpoint.replace(/^\/+/, '')}`;
if (params && Object.keys(params).length > 0) {
const searchParams = new URLSearchParams(params);
fullUrl += (fullUrl.includes('?') ? '&' : '?') + searchParams.toString();
}
// 2. 超时信号控制器
const controller = new AbortController();
const timer = setTimeout(() => {
controller.abort(new Error(`网络请求超时: 超过 ${timeout}ms`));
}, timeout);
// 3. 读取本地持久化凭据(如 JWT Token)
const token = localStorage.getItem('auth_token');
const authHeaders = token ? { 'Authorization': `Bearer ${token}` } : {};
// 4. 组装请求配置
const fetchOptions = {
method,
headers: {
...DEFAULT_CONFIG.headers,
...authHeaders,
...headers
},
signal: controller.signal,
...customOptions
};
// 5. 序列化请求体
if (body) {
if (typeof body === 'object' && !(body instanceof FormData)) {
fetchOptions.body = JSON.stringify(body);
} else {
fetchOptions.body = body;
// 若为 FormData 则删除预设的 application/json 头
if (body instanceof FormData) {
delete fetchOptions.headers['Content-Type'];
}
}
}
try {
const response = await fetch(fullUrl, fetchOptions);
clearTimeout(timer);
// 6. 响应拦截与状态判定
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const error = new Error(errorData.message || `HTTP 错误 ${response.status}`);
error.status = response.status;
error.data = errorData;
throw error;
}
// 7. 处理空内容(如 204 No Content)
if (response.status === 204) {
return null;
}
// 8. 默认按 JSON 解析,若为其他格式降级为文本
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
} catch (err) {
clearTimeout(timer);
// 处理特定状态码(如 401 跳转登录)
if (err.status === 401) {
console.warn('登录已过期,重定向至登录页');
// window.location.href = '/login';
}
throw err;
}
}
// 便捷语法糖
const http = {
get: (url, params, options) => request(url, { ...options, method: 'GET', params }),
post: (url, body, options) => request(url, { ...options, method: 'POST', body }),
put: (url, body, options) => request(url, { ...options, method: 'PUT', body }),
delete: (url, options) => request(url, { ...options, method: 'DELETE' })
};
// 项目业务调用示例:
// async function loadUserProfile() {
// try {
// const userInfo = await http.get('/user/me');
// console.log('当前登录用户:', userInfo);
// } catch (err) {
// console.error('获取信息异常:', err.message);
// }
// }
六、关键避坑要点与工程速查
在使用 fetch 编写业务代码时,除了掌握基础模板外,还应牢记以下几项高频易错细节:
- 响应流单次读取限制(Body Already Read):
Response对象的实体内容是一个只读的数据流(ReadableStream)。调用诸如response.json()、response.text()或response.blob()后,数据流即被消费。如果在同一个 response 上连续执行两次解析,会抛出TypeError: Failed to execute 'json' on 'Response': body stream already read。若必须多次读取,可先调用response.clone()创建一份克隆流。 - 跨域携带 Cookie 凭证(Credentials):默认情况下,跨域请求不会附带当前站点的 Cookie 与 HTTP 认证凭据。如果接口需要基于 Cookie 的 Session 认证,必须在
options中显式声明credentials: 'include'。同时,服务端响应头必须明确设置Access-Control-Allow-Credentials: true,且Access-Control-Allow-Origin不能为通配符*。 - 空响应体解析异常:当服务端返回
204 No Content或没有响应体的空响应时,直接调用await response.json()会导致 JSON 语法解析错误(Unexpected end of JSON input)。在解析前应先检查response.status !== 204,或通过response.text()进行长度兜底判定。 - 组件卸载时的内存泄露防范:在 React、Vue 等前端框架中,异步请求尚未结束而组件已销毁时,如果异步回调继续执行
setState,容易引起状态错乱。通过结合AbortController,在组件销毁钩子(如 React 的useEffect卸载回调)中执行controller.abort(),可彻底杜绝此类悬挂请求。
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13








