为什么这些获取方法是异步的?

Why are these fetch methods asynchronous?

Fetch 是新的基于 Promise 的 API 网络请求:

fetch('https://www.everythingisawesome.com/')
  .then(response => console.log('status: ', response.status));

这对我来说很有意义 - 当我们发起网络调用时,我们 return 一个让我们的线程继续处理其他业务的 Promise。当响应可用时,Promise 中的代码就会执行。

但是,如果我对响应的有效负载感兴趣,我会通过响应的方法而不是属性来实现:

这些方法 return 很有用,但我不清楚为什么。

fetch('https://www.everythingisawesome.com/') //IO bound
  .then(response => response.json()); //We now have the response, so this operation is CPU bound - isn't it?
  .then(entity => console.log(entity.name));

为什么处理响应的负载 return 是一个承诺 - 我不清楚为什么它应该是一个异步操作。

因为内容只有在您开始阅读后才会传输。 headers 先来。

查看实现 here the operation of fetching json is CPU bound because the creation of the response, along with the body is done once the response promise is done. See the implementation of the json function

话虽这么说,但我认为这主要是一个设计概念,因此您可以链接您的承诺处理程序并且只使用一个启动的错误处理程序,无论错误发生在哪个阶段。

像这样:

fetch('https://www.everythingisawesome.com/')
  .then(function(response) {
    return response.json()
  })
  .then(function(json) {
    console.log('parsed json', json)
  })
  .catch(function(ex) {
    console.log('parsing or loading failed', ex)
  })

创建已解决的 promise 的开销非常低。最后,这里不需要使用承诺,但它可以编写出更好看的代码。至少在我看来是这样。

通读 fetch 的实现后,使用 promises 似乎有几个原因。对于初学者,json() 依靠 FileReader 将响应 blob 转换为文本。 FileReadersonload 回调之前不能使用,所以这就是承诺链开始的地方。

function fileReaderReady(reader) {
  return new Promise(function(resolve, reject) {
    reader.onload = function() {
      resolve(reader.result)
    }
    reader.onerror = function() {
      reject(reader.error)
    }
  })
}

从那里开始,额外的承诺用于封装可能发生的特定错误并将它们传播给调用者。例如,如果正文之前已经被读取过一次,如果 blob 没有转换为文本,如果文本没有转换为 JSON,则可能会发生错误。 Promise 在这里很方便,因为这些各种错误中的任何一个都会简单地在调用者的 catch 块中结束。

总而言之,基于承诺的 api 用于读取提取响应,因为: 1. 他们依赖于必须异步初始化的 FileReader。 2. fetch 想要传播读取正文时可能发生的各种错误。 Promises 允许一种统一的方式来做到这一点。

Why are these fetch methods asynchronous?

天真的答案是 "because the specification says so"

  • The arrayBuffer() method, when invoked, must return the result of running consume body with ArrayBuffer.
  • The blob() method, when invoked, must return the result of running consume body with Blob.
  • The formData() method, when invoked, must return the result of running consume body with FormData.
  • The json() method, when invoked, must return the result of running consume body with JSON.
  • The text() method, when invoked, must return the result of running consume body with text.

当然,这并没有真正回答问题,因为它留下了 "Why does the spec say so?"

的问题

这就是事情变得复杂的地方,因为我确定推理,但我没有来自官方来源的证据来证明这一点。我将尝试尽我所能解释理性,但请注意,此处之后的所有内容都应主要视为外部意见。


当您使用 fetch API 从资源请求数据时,您必须等待资源完成下载才能使用它。这应该是相当明显的。 JavaScript 使用异步 APIs 来处理这种行为,这样所涉及的工作就不会阻塞其他脚本,更重要的是 UI.

资源下载完成后,数据量可能很大。没有什么可以阻止您请求超过 50MB 的整体 JSON 对象。

如果您尝试同步解析 50MB 的 JSON,您认为会发生什么?它会阻止其他脚本,更重要的是,UI.

其他程序员已经解决了如何以高性能方式处理大量数据的问题:Streams。在 JavaScript 中,流是使用异步 API 实现的,因此它们不会阻塞,如果您阅读 consume body 的详细信息,很明显流正在用于解析数据:

Let stream be body's stream if body is non-null, or an empty ReadableStream object otherwise.

现在,规范当然可以定义 两种 访问数据的方式:一种同步 API 用于少量数据,一种异步API 用于大量数据,但这会导致混淆和重复。

除了Ya Ain't Gonna Need It。任何可以用同步代码表达的东西都可以用异步代码表达。反之则不然。因此,创建了一个可以处理所有用例的 异步 API。