152

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

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

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

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

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

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


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

4

9 回答 9

58

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

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

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

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

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

于 2016-03-02T12:25:26.350 回答
39

我的解决方案是使用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 上使用这个的例子。

于 2018-05-23T03:20:55.053 回答
17

更新:正如公认的答案所说,现在不可能。但是下面的代码处理了我们的问题一段时间。我应该补充一点,至少我们必须切换到使用基于 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/

于 2019-01-10T21:37:01.810 回答
15

获取:还不可能

听起来,一旦 fetch支持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>

于 2021-10-01T04:59:33.617 回答
7

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

在请求进度方面,它目前缺乏 [与 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 实现中起作用。

于 2016-02-29T23:49:43.587 回答
3

因为没有一个答案能解决问题。

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

于 2017-08-12T00:11:09.287 回答
2

一种可能的解决方法是使用new Request()构造函数然后检查Request.bodyUsed Boolean属性

如果,bodyUsed则属性的 getter 必须返回 true disturbed,否则返回 false。

确定流是否是distributed

实现Bodymixin 的对象被称为disturbedif body为非 null 且其streamdisturbed

返回fetch() Promisefrom inside 链接到when的.then()递归.read()调用等于。ReadableStreamRequest.bodyUsedtrue

请注意,该方法不会读取 的字节,Request.body因为字节会流式传输到端点。此外,上传可以在任何响应完全返回到浏览器之前完成。

const [input, progress, label] = [
  document.querySelector("input")
  , document.querySelector("progress")
  , document.querySelector("label")
];

const url = "/path/to/server/";

input.onmousedown = () => {
  label.innerHTML = "";
  progress.value = "0"
};

input.onchange = (event) => {

  const file = event.target.files[0];
  const filename = file.name;
  progress.max = file.size;

  const request = new Request(url, {
    method: "POST",
    body: file,
    cache: "no-store"
  });

  const upload = settings => fetch(settings);

  const uploadProgress = new ReadableStream({
    start(controller) {
        console.log("starting upload, request.bodyUsed:", request.bodyUsed);
        controller.enqueue(request.bodyUsed);
    },
    pull(controller) {
      if (request.bodyUsed) {
        controller.close();
      }
      controller.enqueue(request.bodyUsed);
      console.log("pull, request.bodyUsed:", request.bodyUsed);
    },
    cancel(reason) {
      console.log(reason);
    }
  });

  const [fileUpload, reader] = [
    upload(request)
    .catch(e => {
      reader.cancel();
      throw e
    })
    , uploadProgress.getReader()
  ];

  const processUploadRequest = ({value, done}) => {
    if (value || done) {
      console.log("upload complete, request.bodyUsed:", request.bodyUsed);
      // set `progress.value` to `progress.max` here 
      // if not awaiting server response
      // progress.value = progress.max;
      return reader.closed.then(() => fileUpload);
    }
    console.log("upload progress:", value);
    progress.value = +progress.value + 1;
    return reader.read().then(result => processUploadRequest(result));
  };

  reader.read().then(({value, done}) => processUploadRequest({value,done}))
  .then(response => response.text())
  .then(text => {
    console.log("response:", text);
    progress.value = progress.max;
    input.value = "";
  })
  .catch(err => console.log("upload error:", err));

}
于 2016-12-19T03:40:15.327 回答
-2
const req = await fetch('./foo.json');
const total = Number(req.headers.get('content-length'));
let loaded = 0;
for await(const {length} of req.body.getReader()) {
  loaded = += length;
  const progress = ((loaded / total) * 100).toFixed(2); // toFixed(2) means two digits after floating point
  console.log(`${progress}%`); // or yourDiv.textContent = `${progress}%`;
}
于 2017-12-11T19:25:02.473 回答
-10

关键部分是ReadableStreamobj_response .body≫。

样本:

let parse=_/*result*/=>{
  console.log(_)
  //...
  return /*cont?*/_.value?true:false
}

fetch('').
then(_=>( a/*!*/=_.body.getReader(), b/*!*/=z=>a.read().then(parse).then(_=>(_?b:z=>z)()), b() ))

您可以在一个巨大的页面上测试运行它,例如https://html.spec.whatwg.org/https://html.spec.whatwg.org/print.pdf。CtrlShiftJ 并加载代码。

(在 Chrome 上测试。)

于 2017-10-21T00:23:59.327 回答