Last updated on

fetch 分析

问题

const response = await fetch("/data");  // (1)
const data = await response.text(); // (2)

为啥要两次 await, 为什么拆分

解释

https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

The fetch() method takes one mandatory argument, the path to the resource you want to fetch. It returns a Promise that resolves to the Response to that request — as soon as the server responds with headers — even if the server response is an HTTP error status.

Once a Response is retrieved, there are a number of methods available to define what the body content is and how it should be handled.

(1) 在 header 有了就 resolve 了 (2) 需要等到 body 整体 resolve

这样设计的好处是可以先读取状态码、headers 等信息,决定后续如何处理

  • text() - 文本格式
  • json() - JSON 格式
  • blob() - 二进制数据
  • formData() - 表单数据
try {
  const response = await fetch("/api/data");
  
  // 可以立即检查状态码
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  // 检查 content-type
  const contentType = response.headers.get("content-type");
  if (contentType && contentType.includes("application/json")) {
    const data = await response.json();  // 解析 JSON
  } else {
    const text = await response.text();  // 作为文本处理
  }
} catch (error) {
  console.error("获取数据失败:", error);
}

https://github.com/sedationh/demo-fetch 这里写了点例子, 除了测试 header 和 body 分离外,还搞了流式获取

Chat With GPT

Note

    res.writeHead(200, {
      "Content-Type": "application/json",
    });
    // 先写入一个空字符串 让 fetch resolve
    res.write("");

我发现必须 res.write(""); 才可以提前返回 header 否则是和 第一个 body chunk 一起返回的

是的,这是一个很好的观察!让我解释一下这个现象:

在 HTTP 协议中,服务器发送响应的顺序是:

  • 状态行(Status Line)
  • 响应头(Headers)
  • 空行
  • 响应体(Body)

当你使用 res.writeHead() 设置响应头时,数据并不会立即发送到客户端。Node.js 会将头信息缓存起来,直到:

  • 第一次调用 res.write()
  • 或者调用 res.end()
// 这样写,headers 会和第一块 body 数据一起发送
res.writeHead(200, {
  "Content-Type": "application/json"
});
res.write(JSON.stringify({ some: "data" }));

// 这样写,headers 会立即发送,因为有一个空的 write
res.writeHead(200, {
  "Content-Type": "application/json"
});
res.write(""); // 触发 headers 发送
res.write(JSON.stringify({ some: "data" }));

这种行为在实现流式响应时特别有用,比如:

// 示例:实现流式响应
res.writeHead(200, {
  "Content-Type": "text/plain",
  "Transfer-Encoding": "chunked"
});
res.write(""); // 立即发送 headers

// 后续可以分块发送数据
setInterval(() => {
  res.write(`数据块 ${Date.now()}\n`);
}, 1000);