上传进度指示器以获取?

Upload progress indicators for fetch?

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

This is the only reference I've found so far,其中指出:

Progress events are a high level feature that won't arrive in fetch for now. You can create your own by looking at the Content-Length header and using a pass-through stream to monitor the bytes received.

This means you can explicitly handle responses without a Content-Length differently. And of course, even if Content-Length is there it can be a lie. With streams you can handle these lies however you want.

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

注意:我Cloudinary JS library感兴趣,因为这取决于 jQuery 而我的应用程序则没有。我只对使用本机 javascript 和 Github 的 fetch polyfill 执行此操作所需的流处理感兴趣。


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

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

it is currently lacking [in comparison to XHR] when it comes to request progression


(旧答案):
Fetch API chapter 中的第一个示例给出了一些有关如何 :

的见解

If you want to receive the body data progressively:

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 constructor antipattern,你可以看到 response.body 是一个流,你可以使用 Reader 从中逐字节读取,你可以触发一个事件或为它们中的每一个做任何你喜欢的事情(例如记录进度)。

但是,Streams spec 似乎还没有完成,我不知道这是否已经在任何提取实现中起作用。

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

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

特定的重定向可能会导致数据重新传输到新位置,但流不会"restart"。我们可以通过将 body 变成一个可以多次调用的回调来解决这个问题,但是我们需要确保暴露重定向的数量不是安全漏洞,因为这是平台 JS 第一次可以检测到。

有些人质疑 link 将消耗流式传输到上传的字节数是否有意义。

长话短说:这还不可能,但将来这将由流处理,或者传递给 fetch().

的某种高级回调来处理

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

The bodyUsed attribute’s getter must return true if disturbed, and false otherwise.

判断流是否为distributed

An object implementing the Body mixin is said to be disturbed if body is non-null and its stream is disturbed.

Return .then() 中的 fetch() Promise 链接到 ReadableStream 的递归 .read() 调用 Request.bodyUsed等于 true.

请注意,当字节流式传输到端点时,该方法不会读取 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));

}

因为 none 个答案解决了问题。

为了实现,您可以检测上传速度with some small initial chunk of known size,上传时间可以用content-length/upload-speed计算。您可以将此时间作为估算值​​。

关键部分是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/ and https://html.spec.whatwg.org/print.pdf 。 CtrlShiftJ 并加载代码。

(在 Chrome 上测试。)

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}%`;
}

我的解决方案是使用 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.

上使用它的示例

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

多亏了这个link:https://jakearchibald.com/2016/streams-ftw/

获取:还不可能

听起来上传进度 最终 可以使用 fetch 一旦它支持 ReadableStream as the body. This is ,但它正在进行中。我认为代码看起来像这样:

警告:此代码还不能运行,仍在等待浏览器支持它

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" />

解决方法:很好的 XMLHttpRequest

代替fetch(),可以使用XMLHttpRequest to track upload progress — the xhr.upload object emits a progress event

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>