【译】为什么要将 fetch 的响应体设计成流?

89 阅读2分钟

原文:Why Make fetch's Response Body a Stream?

by:T.J. Crowder

Émile Bergeron 在推特上问:为什么 fetch 的响应体(Response body)是流(Stream):

有谁能为我解释一下,将响应体设计成可读流(readable stream)有什么优点吗?

在我看来,将响应体解析成 JSON 应该是幂等的,但是有一个我还没注意到的很好的理由将 API 设计成这样。🤔

@tjcrowder 你知道为什么吗? #JavaScript #wat

const response = await fetch(SOME_URL);
response.json(); // fine
response.json();
// TypeError: Failed to execute 'json' on 'Response':
// body stream is locked

我在这方面不是很专业,但是我一直认为这是出于效率的考虑。我的理由来自以下几个方面,将其设计成流意味着:

  1. 从理论上来说,浏览器可以避免将整个 body 加载到内存中,然后再进行处理(如解析为 JSON,加载到 ArrayBuffer 等)。相反,根据你调用的方法,浏览器可以直接将响应的网络流传输到 JSON 解析器、ArrayBuffer 构建器、Blob 构建器等。
  2. 解析等操作可以与读取同时进行(理论上,JSON 中的早期错误将允许它避免继续读取 body)。
  3. Response 对象在你消耗它之后不必保留 body 的副本,以防你再次调用 json() 等;保留结果是使用该响应的代码的工作。

Émile 在推特上指出,你可以 clone 一个 Response,两个 Response 都可以使用 json()

我知道有一些替代方法可以避免这个错误,比如克隆 Response。

const response = await fetch(SOME_URL);
response.clone().json(); // fine!
response.json(); // fine as well

…… 这是正确的操作,但是遵循规范(123),我会避免克隆 Response。因为如果我读得正确,至少直到消耗它们,这会读取 body 并存储两份副本(一份用于克隆,一份用于原始副本)。所以我会使用 text() 然后进行两次 JSON.parse

const response = await fetch(SOME_URL);
if (!response.ok) { // Don't forget the HTTP result check!
    throw new Error("HTTP error " + response.status);
}
let text = await response.text();
const copy1 = JSON.parse(text);
const copy2 = JSON.parse(text);
text = null;

我可能错误地理解了 Émile 的具体意图,但是使用流而不是传递大块数据是一件非常流行的事,而且对我来说确保只有 API 的消费者保留数据是有道理的。

Happy Coding!