Appearance
18|fetch 与前端网络请求
前端页面需要从后端获取数据:服务器列表、任务状态、用户信息。浏览器提供的 fetch API 是发送 HTTP 请求的标准方式,基于 Promise,支持 async/await,可以替代旧的 XMLHttpRequest。
一、基本 GET 请求
js
fetch("/api/servers")
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((err) => {
console.error("请求失败:", err.message);
});fetch 返回 Promise。response.ok 为 true 时表示状态码在 200~299 范围内。response.json() 把响应体解析为 JSON,返回另一个 Promise。
用 async/await 改写:
js
async function loadServers() {
try {
const response = await fetch("/api/servers");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (err) {
console.error("加载服务器列表失败:", err.message);
return [];
}
}二、POST 请求
提交数据时需要配置请求方法和请求头:
js
async function createServer(serverData) {
const response = await fetch("/api/servers", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(serverData),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
// 使用
createServer({ hostname: "web-03", ip: "192.168.1.12" });| 配置项 | 作用 |
|---|---|
method | HTTP 方法:GET、POST、PUT、DELETE、PATCH |
headers | 请求头,Content-Type: application/json 告诉服务端发送的是 JSON |
body | 请求体,必须序列化为字符串(JSON.stringify) |
三、URL 查询参数
js
const params = new URLSearchParams({
page: "1",
limit: "20",
status: "running",
});
const response = await fetch(`/api/servers?${params}`);
// 实际请求:/api/servers?page=1&limit=20&status=runningURLSearchParams 自动处理 URL 编码(如空格转 %20),比字符串拼接更安全。
四、请求头
发送认证 Token
js
const response = await fetch("/api/admin/servers", {
headers: {
"Authorization": `Bearer ${localStorage.getItem("token")}`,
},
});自定义请求头
js
const response = await fetch("/api/servers", {
headers: {
"X-Request-ID": generateId(),
"Accept": "application/json",
},
});五、响应处理
根据状态码处理
js
const response = await fetch("/api/servers/999");
if (response.status === 404) {
console.log("服务器不存在");
return null;
}
if (response.status === 403) {
console.log("无权限访问");
redirectToLogin();
return null;
}
if (!response.ok) {
throw new Error(`请求失败: ${response.status}`);
}
const data = await response.json();解析不同格式的响应
js
// JSON
const json = await response.json();
// 纯文本
const text = await response.text();
// Blob(二进制数据,如图片下载)
const blob = await response.blob();注意:每种解析方法只能调用一次,响应体是流式数据,读取后即消耗。
六、封装请求函数
项目中通常把 fetch 封装一层,统一处理基础 URL、认证头、错误响应:
js
const API_BASE = "/api";
async function request(path, options = {}) {
const url = `${API_BASE}${path}`;
const config = {
headers: {
"Content-Type": "application/json",
...options.headers,
},
...options,
};
const token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = `Bearer ${token}`;
}
const response = await fetch(url, config);
if (!response.ok) {
const err = await response.json().catch(() => ({}));
throw new Error(err.message || `HTTP ${response.status}`);
}
return response.json();
}
// 使用
const servers = await request("/servers");
const newServer = await request("/servers", {
method: "POST",
body: JSON.stringify({ hostname: "web-01" }),
});七、取消请求
长时间未完成的请求需要能取消。AbortController 提供取消能力:
js
const controller = new AbortController();
fetch("/api/slow-endpoint", {
signal: controller.signal,
});
// 5 秒后取消
setTimeout(() => {
controller.abort();
}, 5000);取消后 fetch 返回的 Promise 会被拒绝,错误名为 "AbortError":
js
try {
await fetch("/api/data", { signal: controller.signal });
} catch (err) {
if (err.name === "AbortError") {
console.log("请求已取消");
} else {
console.error("请求失败:", err.message);
}
}八、常见错误
忘记检查 response.ok
js
// 错误:即使返回 404/500,fetch 的 Promise 也会 resolve
const response = await fetch("/api/missing");
const data = await response.json(); // 可能解析的是错误页面 HTML,抛 SyntaxError
// 正确:先检查状态
const response = await fetch("/api/missing");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}请求体未序列化
js
// 错误:直接传对象
fetch("/api/servers", {
method: "POST",
body: { hostname: "web-01" }, // fetch 不会自动转 JSON
});
// 正确:JSON.stringify
fetch("/api/servers", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ hostname: "web-01" }),
});多次读取响应体
js
const response = await fetch("/api/servers");
const data1 = await response.json();
const data2 = await response.json(); // 错误:响应体已消耗需要多次使用时,先保存解析结果:
js
const response = await fetch("/api/servers");
const data = await response.json();
// 之后操作 data 变量跨域请求未处理 CORS
js
// 如果后端没有配置 CORS 头,浏览器会阻止请求
fetch("https://other-domain.com/api");
// 报错:CORS policy: No 'Access-Control-Allow-Origin' header跨域问题需要在服务端配置 CORS 响应头,前端无法绕过浏览器的同源策略。