上传进度指示器以获取?


99

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

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

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

这意味着您可以显式地处理响应,而无需进行Content-Length其他操作。当然,即使Content-Length存在,也可能是谎言。使用流,您可以根据需要处理这些谎言。

我将如何编写发送的“用于监视字节的直通流”?如果有什么不同,我正在尝试执行此操作以增强从浏览器到Cloudinary的图像上传。

注意:我对Cloudinary JS库感兴趣,因为它取决于jQuery,而我的应用程序则不需要。我只对使用本机javascript和Github的polyfill 进行流处理所必需的感兴趣。fetch


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


Answers:


44

流开始在Web平台(https://jakearchibald.com/2016/streams-ftw/)上登陆,但仍处于初期。

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

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

有人质疑将流消耗链接到上载的字节是否有意义。

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


7
太糟糕了。现在接受这个,但是当这变成现实时,我希望其他人可以发布更新的解决方案!:)
neezer

1
更新-使用流显示获取API的进度-twitter.com/umaar/status/917789464658890753/photo/1
Eitan Peer

2
@EitanPeer尼斯。上载是否可以使用类似的功能,例如POST?
迈克尔

4
@EitanPeer但是,问题是关于上传的进度,而不是下载的进度
John Balvin Arias

1
现在是2020年,为什么仍然没有办法做到这一点:(
MHA15

23

我的解决方案是使用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上的 react中使用此示例


2
那也是我的解决方案。Axios似乎非常适合该模具。
杰森·赖斯

1
是否axios使用fetchXMLHttpRequest在引擎盖下?

3
XMLHttpRequest。如果您将其用于本机响应,请注意,与提取相比,XMLHttpRequest解析大型json响应似乎非常慢(慢了大约10倍,并且冻结了整个ui线程)。
Cristiano Coelho

21
不回答问题!如果问题是“您如何在y中进行x?” 说“用z代替x”是不可接受的答案。
德里克·亨德森

3
这不能回答问题,尤其是因为axios它不被使用fetch,也没有这样的支持。我现在确实是他们创作的。
sgammon

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构造函数反模式之外,您还可以看到这response.body是一个Stream,可以使用Reader逐字节读取该Stream,并且可以为每个事件触发一个事件或执行任何您想做的事情(例如记录进度)。

但是,Streams规范似乎还不够完善,我也不知道它是否已经在任何提取实现中起作用。


11
但是,如果我正确地阅读了该示例,则可以通过来下载文件fetch。我感兴趣的进度指标,上传的文件。
neezer '16

糟糕,那句话引用了关于接收字节的信息,这让我感到困惑。
Bergi'3

@Bergi注意,Promise构造函数不是必需的。Response.body.getReader()返回Promise。请参阅如何解决未捕获的RangeError当下载大尺寸的JSON
guest271314

3
@ guest271314是的,我已经把它固定在报价的来源了。不,getReader不返回承诺。不知道这与您链接的帖子有什么关系。
Bergi

@Bergi是的,您.getReader().read()方法正确,返回Promise。那就是试图传达的。该链接暗指前提是,如果可以检查进度以进行下载,则可以检查进度以进行上载。组合一个可在一定程度上返回预期结果的模式;这就是fetch()上传进度。还没有找到一种方法,echo一个BlobFile在的jsfiddle对象,可能是简单的东西。在localhost不模仿网络条件的情况下非常快速地对上传文件进行测试;虽然只是想起Network throttling
guest271314

6

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


2
不错,但是它也适用于上传吗?
内核

@kernel我试图找出来,但是我做不到。而且我也想找到一种上传方法。
Hosseinmp76 '19

同样,但是到目前为止,我并不是很幸运找到/创建一个可以正常运行的上传示例。
内核

1
content-length!==身体的长度。当使用http压缩时(通常是大下载量),content-length是http压缩后的大小,而length是提取文件后的大小。
Ferrybig

@Ferrybig我没明白你的意思。我在某处使用了平等吗?
Hosseinmp76


2

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

bodyUsed属性的getter方法必须返回true,如果disturbed,否则为假。

确定流是否为 distributed

实现Bodymixin 的对象被称为disturbedif if body为非null,其stream为is disturbed

返回fetch() Promise从内部.then()链接到递归.read()的呼叫ReadableStream时,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));

}

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

我想对本杰明·格林巴姆的整个回答表示赞赏。因为我是从他的演讲中学到的。
Leon Gilyadov

@LeonGilyadov可以在任何地方在线获得讲座吗?到源的链接会很好。
Mark Amery

@MarkAmery这里是:youtube.com/watch? v=Ja8GKkxahCo (该演讲在希伯来语中进行)
Leon Gilyadov

11
问题是关于上传,而不是下载。
sarneeh

获取进度的问题是您要上传的时间(下载没有问题)
KamilKiełczewski

-4

关键部分是ReadableStream « obj_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上测试。)


这个答案得到的是负数,但没人能解释为什么要给负数-所以我给+1
KamilKiełczewski

3
它从我那里得到-1,因为它与上无关。
布拉德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.