上传进度指示器以进行提取?

IT技术 javascript html fetch-api
2021-01-28 06:27:52

我正在努力寻找使用fetch实现上传进度指示器的文档或示例

这是迄今为止我找到的唯一参考,其中指出:

进度事件是一项高级功能,目前不会在 fetch 中到达。您可以通过查看Content-Length标头并使用传递流来监视接收到的字节来创建自己的标头。

这意味着您可以明确地处理响应而没有Content-Length不同。当然,即使Content-Length存在也可能是谎言。通过流,您可以随心所欲地处理这些谎言。

我将如何编写“用于监视字节的传递流”发送?如果它有任何不同,我正在尝试这样做以支持从浏览器到Cloudinary 的图像上传

注意:我Cloudinary JS 库感兴趣,因为它依赖于 jQuery 而我的应用程序没有。我只对使用原生 javascript 和 Github 的polyfill执行此操作所需的流处理感兴趣fetch


https://fetch.spec.whatwg.org/#fetch-api

6个回答

Streams 开始登陆网络平台 ( https://jakearchibald.com/2016/streams-ftw/ ),但仍为时尚早。

很快您就可以提供一个流作为请求的主体,但悬而未决的问题是该流的消耗是否与上传的字节有关。

特定的重定向可能会导致数据重新传输到新位置,但流无法“重新启动”。我们可以通过将主体转换为可以多次调用的回调来解决这个问题,但是我们需要确保暴露重定向次数不是安全漏洞,因为这是平台上的第一次 JS 可以检测到。

有些人质疑将流消耗与上传的字节数联系起来是否有意义。

长话短说:这还不可能,但将来这将通过流或某种更高级别的回调传递到fetch().

@EitanPeer 但是,问题在于上传进度,而不是下载进度
2021-03-15 06:27:52
现在是 2020 年,为什么仍然没有办法做到这一点:(
2021-04-05 06:27:52
太糟糕了。暂时接受这个,但是当这成为现实时,我希望其他人会发布更新的解决方案!:)
2021-04-08 06:27:52
@EitanPeer 不错。类似的事情是否适用于上传,例如 POST?
2021-04-12 06:27:52
现在都2021了,还是什么都没有?
2021-04-13 06:27:52

我的解决方案是使用axios,它很好地支持这一点:

axios.request({
    method: "post", 
    url: "/aaa", 
    data: myData, 
    onUploadProgress: (p) => {
      console.log(p); 
      //this.setState({
          //fileprogress: p.loaded / p.total
      //})
    }
}).then (data => {
    //this.setState({
      //fileprogress: 1.0,
    //})
})

我有在github上使用它的示例

这不能回答问题,特别是因为axios不在fetch幕后使用,并且没有这样的支持。我现在正在他们创作它
2021-03-14 06:27:52
那也是我的解决方案。Axios 似乎非常适合模具。
2021-03-25 06:27:52
是否axios使用fetchXMLHttpRequest在引擎盖下?
2021-03-30 06:27:52
不回答问题!如果问题是“你如何在 y 中做 x?” 说“在 z 中做 x”不是一个可以接受的答案。
2021-04-09 06:27:52
XMLHttpRequest。如果您将此用于本机反应,请注意 XMLHttpRequest 与 fetch 相比,解析大型 json 响应的速度似乎非常慢(大约慢 10 倍,并且它冻结了整个 ui 线程)。
2021-04-10 06:27:52

更新:正如接受的答案所说,现在不可能了。但下面的代码处理了我们的问题一段时间。我应该补充一点,至少我们必须切换到使用基于 XMLHttpRequest 的库。

const response = await fetch(url);
const total = Number(response.headers.get('content-length'));

const reader = response.body.getReader();
let bytesReceived = 0;
while (true) {
    const result = await reader.read();
    if (result.done) {
        console.log('Fetch complete');
        break;
    }
    bytesReceived += result.value.length;
    console.log('Received', bytesReceived, 'bytes of data so far');
}

感谢此链接:https : //jakearchibald.com/2016/streams-ftw/

不错,但它也适用于上传吗?
2021-03-14 06:27:52
@kernel 我试图找出答案,但我无法做到。我也喜欢找到一种方法来上传。
2021-03-18 06:27:52
您的代码假定内容标头长度指定提取将要下载的字节数。这并不总是正确的,因此您的代码无法向用户显示进度,因为bytesReceived变得大于total
2021-03-21 06:27:52
content-length!== 身体的长度。使用http压缩时(大下载比较常见),content-length是http压缩后的大小,length是文件解压后的大小。
2021-03-29 06:27:52
此外,即使浏览器事先也不知道实际的内容长度。您将获得的只是一个压缩后的进度指示器。例如,如果您正在下载压缩率分布不均的 zip 文件(有些文件是随机的,有些是低熵文件),您会注意到进度指示器严重倾斜。
2021-03-30 06:27:52

获取:尚不可能

这听起来像上传进度,最终有可能与取一次它支持ReadableStream作为body目前尚未实施,但正在进行中。我认为代码看起来像这样:

警告:此代码尚未生效,仍在等待浏览器支持

async function main() {
  const blob = new Blob([new Uint8Array(10 * 1024 * 1024)]); // any Blob, including a File
  const progressBar = document.getElementById("progress");

  const totalBytes = blob.size;
  let bytesUploaded = 0;

  const blobReader = blob.stream().getReader();
  const progressTrackingStream = new ReadableStream({
    async pull(controller) {
      const result = await blobReader.read();
      if (result.done) {
        console.log("completed stream");
        controller.close();
        return;
      }
      controller.enqueue(result.value);
      bytesUploaded += result.value.byteLength;
      console.log("upload progress:", bytesUploaded / totalBytes);
      progressBar.value = bytesUploaded / totalBytes;
    },
  });
  const response = await fetch("https://httpbin.org/put", {
    method: "PUT",
    headers: {
      "Content-Type": "application/octet-stream"
    },
    body: progressTrackingStream,
  });
  console.log("success:", response.ok);
}
main().catch(console.error);
upload: <progress id="progress" />

解决方法:好 ol' XMLHttpRequest

代替fetch(),可以使用 XMLHttpRequest 来跟踪上传进度——该xhr.upload对象发出一个progress事件。

async function main() {
  const blob = new Blob([new Uint8Array(10 * 1024 * 1024)]); // any Blob, including a File
  const uploadProgress = document.getElementById("upload-progress");
  const downloadProgress = document.getElementById("download-progress");

  const xhr = new XMLHttpRequest();
  const success = await new Promise((resolve) => {
    xhr.upload.addEventListener("progress", (event) => {
      if (event.lengthComputable) {
        console.log("upload progress:", event.loaded / event.total);
        uploadProgress.value = event.loaded / event.total;
      }
    });
    xhr.addEventListener("progress", (event) => {
      if (event.lengthComputable) {
        console.log("download progress:", event.loaded / event.total);
        downloadProgress.value = event.loaded / event.total;
      }
    });
    xhr.addEventListener("loadend", () => {
      resolve(xhr.readyState === 4 && xhr.status === 200);
    });
    xhr.open("PUT", "https://httpbin.org/put", true);
    xhr.setRequestHeader("Content-Type", "application/octet-stream");
    xhr.send(blob);
  });
  console.log("success:", success);
}
main().catch(console.error);
upload: <progress id="upload-progress"></progress><br/>
download: <progress id="download-progress"></progress>

我不认为这是可能的。草案指出:

请求进度方面,它目前缺乏 [与 XHR 相比]


(旧答案):Fetch API 章节中
的第一个示例提供了一些有关如何:

如果要逐步接收正文数据:

function consume(reader) {
  var total = 0
  return new Promise((resolve, reject) => {
    function pump() {
      reader.read().then(({done, value}) => {
        if (done) {
          resolve()
          return
        }
        total += value.byteLength
        log(`received ${value.byteLength} bytes (${total} bytes in total)`)
        pump()
      }).catch(reject)
    }
    pump()
  })
}

fetch("/music/pk/altes-kamuffel.flac")
  .then(res => consume(res.body.getReader()))
  .then(() => log("consumed the entire body without keeping the whole thing in memory!"))
  .catch(e => log("something went wrong: " + e))

除了它们对Promise构造函数 antipattern的使用之外,您可以看到这response.body是一个 Stream,您可以使用 Reader 从中逐字节读取,并且您可以触发事件或为每个人做任何您喜欢的事情(例如记录进度)。

然而,Streams 规范似乎还没有完全完成,我不知道这是否已经在任何 fetch 实现中起作用。

@Bergi 是的,你是.getReader()对的,.read()方法返回一个Promise. 这就是试图传达的意思。该链接是在暗示如果下载可以检查进度,则上传可以检查进度的前提。组合一个模式,在可观的程度上返回预期的结果;这是fetch()上传的进度还没有找到一种方法,echo一个BlobFile在的jsfiddle对象,可能是简单的东西。localhost不模拟网络条件的情况下非常快速地测试上传文件;虽然才想起Network throttling
2021-03-15 06:27:52
如果我正确读取这个例子,不过,这将是下载通过文件fetch我对上传文件的进度指示器感兴趣
2021-03-18 06:27:52
@guest271314 是的,我已经在引用的来源修复了它不,getReader不返回Promise。不知道这与您链接的帖子有什么关系。
2021-03-28 06:27:52
糟糕,那句话是关于接收字节的,这让我很困惑。
2021-04-07 06:27:52
@Bergi 注意,Promise构造函数不是必需的。Response.body.getReader()返回一个Promise. 下载大尺寸json时如何解决Uncaught RangeError
2021-04-07 06:27:52