2016-02-29 99 views
37

我很努力地找到使用fetch实现上传进度指示器的文档或示例。上传进度指示器以获取?

This is the only reference I've found so far,其中规定:

进度事件是一个高层次的功能,不会到达取现在。您可以通过查看Content-Length标题并使用传递流来监控收到的字节来创建您自己的标题。

这意味着您可以明确处理响应,而不需要Content-Length。当然,即使在那里,它也可能是一个谎言。不过,你可以通过流处理这些谎言。

我该如何写“传递流来监控字节”发送?如果它有什么不同,我试图这样做,以从浏览器向Cloudinary上传图片。

注意:我感兴趣的Cloudinary JS library,因为它依赖于jQuery和我的应用程序没有。我只对使用原生JavaScript和Github的fetch polyfill进行流处理很感兴趣。


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

+3

@Magix参见[中止取:下一代#447]。(HTTPS:// github上。com/whatwg/fetch/issues/447) – guest271314

回答

23

流开始登陆网络平台(https://jakearchibald.com/2016/streams-ftw/),但它仍然是早期。

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

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

有人质疑是否有必要将流消费链接到上传的字节。长话短说:这是不可能的,但在未来,这将通过流,或某种更高级别的回调传递到fetch()处理。

+4

太糟糕了。现在就接受这个,但是当这成为现实时,我希望别人会发布更新的解决方案! :) – neezer

+0

@ jaffa-the-cake的任何消息? – mu3

+1

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

5

我不认为这是可能的。状态草案:

它是目前所缺乏[相比XHR]当涉及到请求进展


(旧回答):
在第一个例子Fetch API chapter提供了一些有关如何:

如果要逐步接受身体数据:

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似乎没有完全完成,我不知道这是否已经在任何获取实现工作。

+5

但是,如果我正确地阅读了这个例子,这可能是**通过'fetch'下载**文件。我对**上传**文件的进度指示器感兴趣。 – neezer

+0

哎呀,那引用关于*接收*字节的说法,这让我很困惑。 – Bergi

+0

@Bergi注意,'Promise'构造函数是没有必要的。 'Response.body.getReader()'返回一个'Promise'。见[下载大尺寸json时如何解决Uncaught RangeError](http://stackoverflow.com/questions/39959467/how-to-solve-uncaught-rangeerror-when-download-large-size-json) – guest271314

2

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

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

,以确定是否流是distributed

实施Body混入的对象被认为是disturbed如果 body为非空并且其streamdisturbed是。

返回的fetch()Promise.then()内链接到递归.read()呼叫的ReadableStreamRequest.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

由于没有一个答案解决问题。

只是为了实现,您可以检测上传速度with some small initial chunk of known size,上传时间可以通过content-length/upload-speed来计算。您可以使用这段时间作为估计。

+1

在我们等待实时解决方案时使用非常聪明,好用的技巧:) – Magix

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

这将不会工作,因为'ReadableStreamDefaultReader'没有'byteLength'属性。 – silicakes

+0

我想给本杰明格鲁恩鲍姆一个完整的答案。因为我从他的演讲中学到了。 –