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

Why are these fetch methods asynchronous?

本文关键字:异步 方法 获取 为什么      更新时间:2023-09-26

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

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

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

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

  • arrayBuffer ()
  • blob ()
  • formData ()
  • json ()
  • text ()

这些方法返回承诺,我不清楚为什么。

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));

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

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

最基本的答案是"因为规范这么说"

  • arrayBuffer()方法,当被调用时,必须返回使用ArrayBuffer运行消费体的结果。
  • blob()方法,当被调用时,必须返回使用Blob运行消费体的结果。
  • formData()方法,当调用时,必须返回使用FormData运行消费体的结果。
  • json()方法,当调用时,必须返回使用JSON运行消费体的结果。
  • text()方法,当被调用时,必须返回带有文本的消费体运行的结果。

当然,这并没有真正回答问题,因为它留下了一个问题:"为什么规范这么说?"

这就是它变得复杂的地方,因为我确定推理,但我没有来自官方来源的证据来证明它。我将尽我所能解释其中的原因,但请注意,在此之后的一切都应被视为外部意见。


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

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

如果你试图同步解析50MB的JSON,你认为会发生什么?它会阻止其他脚本,更重要的是会阻止UI。 其他程序员已经解决了如何以高性能的方式处理大量数据:Streams。在JavaScript中,流是使用异步API实现的,这样它们就不会阻塞,如果你阅读消费体的详细信息,很明显流是用来解析数据的:

如果body为非空,则stream为body的流,否则为空ReadableStream对象。

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

除此之外,你不需要它。所有可以用同步代码表示的东西都可以用异步代码表示。反之则不然。因此,创建了可以处理所有用例的单个异步 API。

因为内容在你开始阅读之前是不会被传输的。头是第一位的。

看看这里的实现,获取json的操作是CPU绑定的,因为响应的创建以及响应体是在响应承诺完成后完成的。参见json函数的实现

话虽如此,我认为这主要是一个设计概念,所以你可以链接你的承诺处理程序,只使用一个错误处理程序,不管错误发生在哪个阶段。

:

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的实现之后,似乎有几个原因使用promise。对于初学者来说,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,则可能会发生错误。承诺在这里很方便,因为这些不同的错误中的任何一个都会在调用者的catch块中结束。

所以在结论中,一个基于承诺的api被用于读取读取响应,因为:1. 它们依赖于必须异步初始化自身的FileReader。2. Fetch想要传播在读取主体时可能发生的各种错误。