Skip to content

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.oktrue 时表示状态码在 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" });
配置项作用
methodHTTP 方法:GETPOSTPUTDELETEPATCH
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=running

URLSearchParams 自动处理 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 响应头,前端无法绕过浏览器的同源策略。