2016-02-29 96 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

我想給本傑明格魯恩鮑姆一個完整的答案。因爲我從他的演講中學到了。 –