本文深入讲解在现代 JavaScript 中使用 fetch API 发送 GET 请求的标准写法,详细分析 async/await 模式、response.ok 状态检查、查询参数安全拼接、超时控制与完整异常处理方案。
JavaScript使用fetch API发送GET请求的标准写法
时间:2026-11-03
浏览:399
文章目录
一、标准 GET 请求的基础范本
在日常业务中,最简洁且稳健的 GET 请求应当兼顾可读性、状态校验与错误兜底。以下是一个直接可运行的标准函数示例:
// 标准 GET 请求函数
async function fetchUserData(userId) {
const url = `https://api.example.com/users/${userId}`;
try {
// 发起请求并注入 8 秒超时信号
const response = await fetch(url, {
method: 'GET', // GET 为默认值,显式写出或省略均可
headers: {
'Accept': 'application/json'
},
signal: AbortSignal.timeout(8000)
});
// 步骤关键点:必须主动校验 HTTP 状态码是否正常
if (!response.ok) {
throw new Error(`HTTP 错误 [${response.status}]: ${response.statusText}`);
}
// 异步流式解析 JSON 数据体
const data = await response.json();
return data;
} catch (error) {
// 统一捕获网络中断、超时、HTTP异常及解析错误
console.error('请求失败详情:', error.message);
throw error; // 向调用方抛出错误,以便上层进行 UI 提示
}
}
这段范本之所以被视为“标准写法”,是因为它规避了早期 .then().catch() 链式调用中容易出现的逻辑断裂,完整覆盖了请求从发出到落地的每一个必要环节。
二、标准写法的核心步骤与执行机制
理解上述代码不能仅靠死记硬背,必须明白浏览器底层处理 fetch 时的时序与状态变化。一个完整的 GET 流程包含五个确定步骤:
1. 明确省略或配置 GET 方法
在 fetch API 中,当你不传递第二个配置参数,或者配置对象中不指定 method 时,浏览器的底层默认行为就是 GET 请求。因此 fetch(url) 与 fetch(url, { method: 'GET' }) 效果一致。在团队工程中,通常根据团队规范选择是否显式声明 method。
2. 两阶段决议(Two-Phase Resolution)
fetch 设计为流式(Streams)传输,整个异步过程分为两个独立阶段:
- 第一阶段(首部到达):当服务端返回 HTTP 响应头(Headers)并确定状态码时,
await fetch()的 Promise 即宣告决议(Resolved),并返回一个Response对象。此时响应体(Body)的内容可能还在网络传输中。 - 第二阶段(数据体解析):调用
response.json()或response.text()是第二个异步操作,它会从网络底层以流的形式持续读取二进制数据,全部下载完毕后执行序列化解析。因此必须同样使用await。
3. 严格检查 response.ok 属性
response.ok 是一个布尔值只读属性。只要 HTTP 状态码处于 200 ~ 299 区间内,该属性即为 true。一旦服务端返回了 400、401、403、404、500 等状态码,response.ok 将变为 false。在此处手动 throw new Error 是阻断错误数据污染业务逻辑的最关键屏障。
三、避免最常见陷阱:为什么 404 和 500 不会进入 catch?
许多初学者经常会写出如下具有重大安全隐患的代码:
// 错误示范:误以为 catch 能兜底所有异常
try {
const res = await fetch('/api/non-existent');
const data = await res.json(); // 若服务端 404 返回 HTML 错误页,此处将报 JSON 语法错误
} catch (err) {
// 这里原本想捕获接口异常,结果只能捕获 JSON 解析失败!
}
这里存在的根本机制差异在于:fetch API 只在遇到“网络层严重故障”时才会将 Promise 标记为 rejected。
| 故障场景 | 典型原因 | Promise 状态 | 是否自动触发 catch | 标准解决手段 |
|---|---|---|---|---|
| 网络传输级故障 | 断网、DNS 解析错误、CORS 跨域被拦截、连接超时 | Rejected (抛出 TypeError / AbortError) |
是 | 外层 try...catch 兜底,提示用户检查网络 |
| 服务端 HTTP 报错 | 404 未找到资源、500 服务端内部错误、401 未登录 | Resolved (成功返回 Response 实例) |
否 | 显式检查 if (!response.ok) throw ... |
| 响应内容格式错误 | 服务端在 500 时返回了 HTML 网页而非约定 JSON | 解析方法 Promise Rejected |
是 | 先拦截 !response.ok,杜绝直接对错误页调用 json() |
四、生产级必备:动态参数、请求头与超时控制
在实际生产环境中,一个纯粹的 URL 往往不能满足复杂查询的需求。标准写法必须妥善解决查询参数转义、HTTP 协商头以及超时释放等工程问题。
1. 使用 URLSearchParams 处理查询字符串
严禁使用普通的字符串加号拼接或模板字符串拼接 URL 参数。例如当参数值包含 &、=、空格或中文字符时,手动拼接会导致解析截断或报错。标准写法是借助原生 URL 和 URLSearchParams 对象:
// 规范的 GET 查询参数装配方法
function buildQueryUrl(endpoint, params = {}) {
const url = new URL(endpoint, window.location.origin);
// 使用 searchParams 自动完成转义与格式化
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.set(key, String(value));
}
});
return url.toString();
}
// 示例调用:自动处理中文字符和特殊符号
const requestUrl = buildQueryUrl('/api/search', {
keyword: '前端 & 架构',
page: 1,
limit: 20
});
// 生成: /api/search?keyword=%E5%89%8D%E7%AB%AF+%26+%E6%9E%B6%E6%9E%84&page=1&limit=20
2. 超时控制:AbortSignal.timeout 与 AbortController
原生 fetch 默认不设超时时间,若后端无响应,该请求可能会在浏览器中长时间保持挂起,占用浏览器并发连接数并导致前端界面卡死。
- 简易超时(现代浏览器标准):直接在 options 中传入
signal: AbortSignal.timeout(8000),8 秒未完成即自动中止。 - 组件解绑与联动取消:在单页应用(React / Vue)中,若组件在请求返回前已经卸载,可使用
const controller = new AbortController(),并将controller.signal传给 fetch,在组件清理钩子中调用controller.abort()。
body 字段(哪怕是空对象或空字符串),浏览器在解析阶段就会直接抛出 TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body。
五、可直接复用的工程级封装
在项目中,我们通常将上述标准统一封装为一个通用的 GET 工具函数:
/**
* 标准工程级 GET 请求封装
* @param {string} url - 目标路径或完整URL
* @param {Object} options - 请求配置选项
* @param {Object} [options.params] - 查询参数键值对
* @param {Object} [options.headers] - 自定义请求头
* @param {number} [options.timeout=10000] - 超时毫秒数
* @returns {Promise<any>} 解析后的 JSON 数据
*/
async function httpGet(url, { params, headers = {}, timeout = 10000, ...customConfig } = {}) {
// 1. 组装具备转义能力的 URL
const finalUrl = new URL(url, window.location.href);
if (params) {
Object.entries(params).forEach(([k, v]) => {
if (v !== undefined && v !== null) {
finalUrl.searchParams.set(k, String(v));
}
});
}
// 2. 超时控制器配置
const signal = customConfig.signal || AbortSignal.timeout(timeout);
try {
const response = await fetch(finalUrl.toString(), {
method: 'GET',
headers: {
'Accept': 'application/json',
...headers
},
signal,
...customConfig
});
// 3. HTTP 业务状态码拦截
if (!response.ok) {
let errorBody = null;
try {
errorBody = await response.json();
} catch (_) {
errorBody = await response.text().catch(() => null);
}
const err = new Error(`请求失败,状态码: ${response.status}`);
err.status = response.status;
err.data = errorBody;
throw err;
}
// 4. 解析并交付响应体
return await response.json();
} catch (error) {
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
console.warn(`GET 请求超时或已被中断: ${url}`);
}
throw error;
}
}
六、标准写法快速自查清单
在编写或审查 GET 请求代码时,可对照以下清单快速核验是否符合工业级标准:
- 语法标准:是否使用
async/await保持单层可读性?是否将await response.json()放入了同一结构中? - 状态拦截:是否在读取响应体前,强制执行了
if (!response.ok)检查? - 参数处理:是否使用
URLSearchParams进行查询参数构造,彻底杜绝了手动加号拼装? - 健壮容错:是否设定了超时时间(
AbortSignal.timeout)?外层catch能否同时应对断网与服务端报错? - 协议合规:是否确保没有误写
body参数?
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。