上传获取的进度指标?

nee*_*zer 74 javascript html5 fetch-api

我很难找到使用fetch实现上传进度指示器的文档或示例.

这是迄今为止我发现的唯一参考资料,其中指出:

进度事件是一个高级功能,暂时无法获取.您可以通过查看Content-Length标头并使用传递流来监视接收的字节来创建自己的标头.

这意味着您可以无需另外明确地处理响应Content-Length.当然,即使在Content-Length那里也可能是谎言.使用流可以根据需要处理这些谎言.

如何编写"传输流来监控字节"发送?如果它产生任何差异,我正在尝试这样做以支持从浏览器到Cloudinary的图像上传.

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


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

jtb*_*des 69

获取:仅限 Chrome

\n

浏览器正在努力支持ReadableStream作为 fetch body。对于 Chrome,这是从 v105 开始实施的对于其他浏览器,目前尚未实现。

\n

(请注意,duplex: "half"当前需要使用带有 fetch 的流主体。)

\n

自定义TransformStream可用于跟踪进度。这是一个工作示例:

\n

警告:此代码不适用于 Chrome 以外的浏览器

\n

\r\n
\r\n
async function main() {\n  const blob = new Blob([new Uint8Array(10 * 1024 * 1024)]); // any Blob, including a File\n  const uploadProgress = document.getElementById("upload-progress");\n  const downloadProgress = document.getElementById("download-progress");\n\n  const totalBytes = blob.size;\n  let bytesUploaded = 0;\n\n  // Use a custom TransformStream to track upload progress\n  const progressTrackingStream = new TransformStream({\n    transform(chunk, controller) {\n      controller.enqueue(chunk);\n      bytesUploaded += chunk.byteLength;\n      console.log("upload progress:", bytesUploaded / totalBytes);\n      uploadProgress.value = bytesUploaded / totalBytes;\n    },\n    flush(controller) {\n      console.log("completed stream");\n    },\n  });\n  const response = await fetch("https://httpbin.org/put", {\n    method: "PUT",\n    headers: {\n      "Content-Type": "application/octet-stream"\n    },\n    body: blob.stream().pipeThrough(progressTrackingStream),\n    duplex: "half",\n  });\n  \n  // After the initial response headers have been received, display download progress for the response body\n  let success = true;\n  const totalDownloadBytes = response.headers.get("content-length");\n  let bytesDownloaded = 0;\n  const reader = response.body.getReader();\n  while (true) {\n    try {\n      const { value, done } = await reader.read();\n      if (done) {\n        break;\n      }\n      bytesDownloaded += value.length;\n      if (totalDownloadBytes != undefined) {\n        console.log("download progress:", bytesDownloaded / totalDownloadBytes);\n        downloadProgress.value = bytesDownloaded / totalDownloadBytes;\n      } else {\n        console.log("download progress:", bytesDownloaded, ", unknown total");\n      }\n    } catch (error) {\n      console.error("error:", error);\n      success = false;\n      break;\n    }\n  }\n  \n  console.log("success:", success);\n}\nmain().catch(console.error);
Run Code Online (Sandbox Code Playgroud)\r\n
upload: <progress id="upload-progress"></progress><br/>\ndownload: <progress id="download-progress"></progress>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

解决方法:好的 XMLHttpRequest

\n

相反fetch(),可以使用XMLHttpRequest来跟踪上传进度\xe2\x80\x94xhr.upload对象发出一个progress事件

\n

\r\n
\r\n
async function main() {\n  const blob = new Blob([new Uint8Array(10 * 1024 * 1024)]); // any Blob, including a File\n  const uploadProgress = document.getElementById("upload-progress");\n  const downloadProgress = document.getElementById("download-progress");\n\n  const xhr = new XMLHttpRequest();\n  const success = await new Promise((resolve) => {\n    xhr.upload.addEventListener("progress", (event) => {\n      if (event.lengthComputable) {\n        console.log("upload progress:", event.loaded / event.total);\n        uploadProgress.value = event.loaded / event.total;\n      }\n    });\n    xhr.addEventListener("progress", (event) => {\n      if (event.lengthComputable) {\n        console.log("download progress:", event.loaded / event.total);\n        downloadProgress.value = event.loaded / event.total;\n      }\n    });\n    xhr.addEventListener("loadend", () => {\n      resolve(xhr.readyState === 4 && xhr.status === 200);\n    });\n    xhr.open("PUT", "https://httpbin.org/put", true);\n    xhr.setRequestHeader("Content-Type", "application/octet-stream");\n    xhr.send(blob);\n  });\n  console.log("success:", success);\n}\nmain().catch(console.error);
Run Code Online (Sandbox Code Playgroud)\r\n
upload: <progress id="upload-progress"></progress><br/>\ndownload: <progress id="download-progress"></progress>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

  • 如果您运行上面的 XHR 示例代码,您将看到它适用于请求和响应正文进度。这些是 XMLHttpRequest 上的单独事件侦听器。对于 `fetch()`,[`response.body`](https://developer.mozilla.org/en-US/docs/Web/API/Response/body) 是一个可用于跟踪下载进度的流。 (2认同)

Jaf*_*ake 36

Streams开始登陆网络平台(https://jakearchibald.com/2016/streams-ftw/),但现在还处于早期阶段.

很快你就能够提供一个流作为请求的主体,但是开放的问题是该流的消耗是否与上传的字节有关.

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

有些人质疑将流消耗链接到上传的字节是否有意义.

长话短说:这还不可能,但将来这将由流处理,或者传递给某种更高级别的回调fetch().

  • 现在都2020年了,为什么还是没有办法:( (9认同)
  • 现在已经2021年了,还没有消息吗? (8认同)
  • 太糟糕了.现在接受这个,但是当这成为现实时,我希望其他人会发布更新的解决方案!:) (6认同)
  • @1.21gigawatts - 我们回到了未来 (3认同)
  • 2023年一定是这一年 (3认同)
  • @EitanPeer 不错。类似的事情是否适用于上传,例如 POST? (2认同)
  • @EitanPeer但是,问题是关于上传进度,而不是下载进度 (2认同)
  • 获取上传流现在是 Chrome 的一部分(实验性)请参阅 https://chromestatus.com/feature/5274139738767360 ,它将允许读取传输的字节。希望其他浏览器也能实现这一点。 (2认同)

Ric*_*Dev 25

正如其他答案中已经解释的那样,使用 是不可能的fetch,但是使用 XHR 是不可能的。这是我的更紧凑的 XHR 解决方案:

const uploadFiles = (url, files, onProgress) =>
  new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.upload.addEventListener('progress', e => onProgress(e.loaded / e.total));
    xhr.addEventListener('load', () => resolve({ status: xhr.status, body: xhr.responseText }));
    xhr.addEventListener('error', () => reject(new Error('File upload failed')));
    xhr.addEventListener('abort', () => reject(new Error('File upload aborted')));
    xhr.open('POST', url, true);
    const formData = new FormData();
    Array.from(files).forEach((file, index) => formData.append(index.toString(), file));
    xhr.send(formData);
  });
Run Code Online (Sandbox Code Playgroud)

适用于一个或多个文件。

如果您有这样的文件输入元素:

<input type="file" multiple id="fileUpload" />
Run Code Online (Sandbox Code Playgroud)

像这样调用该函数:

document.getElementById('fileUpload').addEventListener('change', async e => {
  const onProgress = progress => console.log('Progress:', `${Math.round(progress * 100)}%`);
  const response = await uploadFiles('/api/upload', e.currentTarget.files, onProgress);
  if (response.status >= 400) {
    throw new Error(`File upload failed - Status code: ${response.status}`);
  }
  console.log('Response:', response.body);
}
Run Code Online (Sandbox Code Playgroud)

也适用于构建文件放置区域时e.dataTransfer.files从事件中获得的信息。drop


dwj*_*ton 16

我的解决方案是使用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,
        //})
      })
Run Code Online (Sandbox Code Playgroud)

我有一个在github上使用它的例子.

  • 不回答问题!如果问题是“你如何在 y 中做 x ?” 说“改为在 z 中执行 x”不是一个可接受的答案。 (60认同)
  • 我同意这不是特定问题的解决方案,但考虑到特定问题没有解决方案,我投票赞成这个答案。 (11认同)
  • @DerekHenderson如果真正的答案是“你不能在y中做x”那么“在z中做x”可能对很多人有用。 (9认同)
  • 这并不能回答问题,特别是因为 `axios` 在底层不使用 `fetch`,并且没有这样的支持。我现在正在为他们创作它。 (7认同)
  • `axios` 是否在底层使用了 `fetch` 或 `XMLHttpRequest`? (3认同)
  • 那也是我的解决方案。Axios似乎非常合适。 (2认同)
  • XMLHttpRequest。如果您使用它来进行本机响应,请注意,与获取相比,XMLHttpRequest解析大型json响应似乎非常慢(慢了大约10倍,并且冻结了整个ui线程)。 (2认同)

Hos*_*p76 15

更新:正如接受的答案所说,现在不可能了。但下面的代码处理了我们的问题一段时间。我应该补充一点,至少我们必须切换到使用基于 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');
}
Run Code Online (Sandbox Code Playgroud)

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

  • 不错,但它也适用于上传吗? (6认同)
  • `content-length` !== 正文的长度。当使用http压缩时(常见于大下载),content-length是http压缩后的大小,而length是文件解压后的大小。 (3认同)
  • 您的代码假设内容标头长度指定提取将要下载的字节数。这并不总是正确的,因此您的代码无法向用户显示进度,因为“bytesReceived”变得大于“total” (2认同)
  • 而且,甚至浏览器事先也不知道实际的内容长度。您将得到的只是压缩后进度指示器。例如,如果您正在下载压缩比分布不均匀的 zip 文件(有些文件是随机的,有些文件是低熵的),您会注意到进度指示器严重倾斜。 (2认同)

Gab*_*iel 11

使用 fetch:现在可以使用 Chrome >= 105

如何: https: //developer.chrome.com/articles/fetch-streaming-requests/

目前其他浏览器不支持(也许您阅读本文时会出现这种情况,请相应地编辑我的答案)

特征检测(来源

const supportsRequestStreams = (() => {
  let duplexAccessed = false;

  const hasContentType = new Request('', {
    body: new ReadableStream(),
    method: 'POST',
    get duplex() {
      duplexAccessed = true;
      return 'half';
    },
  }).headers.has('Content-Type');

  return duplexAccessed && !hasContentType;
})();
Run Code Online (Sandbox Code Playgroud)

需要 HTTP >= 2

如果连接是 HTTP/1.x,则提取将被拒绝。


Ber*_*rgi 8

我不认为这是可能的.草案规定:

当涉及到请求进展时,它目前缺乏[ 与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))
Run Code Online (Sandbox Code Playgroud)

除了使用Promise构造函数反模式之外,您还可以看到这response.body是一个Stream,您可以使用Reader逐字节读取,并且可以为每个事件触发事件或执行任何操作(例如,记录进度).

但是,Streams规范似乎没有完全完成,我不知道这是否已经适用于任何fetch实现.

  • 但是,如果我正确地阅读了这个例子,这将是**通过`fetch`下载**文件.我对**上传**文件的进度指标很感兴趣. (7认同)
  • @ guest271314是的,[我已经在源头修改了它](https://github.com/whatwg/fetch/pull/227).不,[`getReader`](https://streams.spec.whatwg.org/#rs-get-reader)不会返回承诺.不知道这与你链接的帖子有什么关系. (3认同)

Shi*_*ora 5

因为所有答案都不能解决问题。

出于实现的目的,您可以使用一些已知大小的小初始块来检测上传速度,并且可以使用内容长度/上传速度来计算上传时间。您可以将此时间用作估计。

  • 对我来说太冒险了。不想[像Windows复制文件进度条一样结束](https://superuser.com/questions/43562/windows-file-copy-dialog-why-is-the-estimation-so-bad) (34认同)
  • 非常聪明,在我们等待实时解决方案时可以使用的好技巧:) (3认同)
  • 不可靠、复杂并且会显示不正确的值。 (2认同)