上传进度指示器以获取?
Posted
技术标签:
【中文标题】上传进度指示器以获取?【英文标题】:Upload progress indicators for fetch? 【发布时间】:2016-06-13 05:03:20 【问题描述】:我正在努力寻找使用fetch 实现上传进度指示器的文档或示例。
This is the only reference I've found so far,其中声明:
进度事件是一项高级功能,目前无法获取。您可以通过查看
Content-Length
标头并使用传递流来监控接收到的字节来创建自己的。这意味着您可以以不同方式显式处理没有
Content-Length
的响应。当然,即使有Content-Length
也可能是谎言。通过流,您可以随心所欲地处理这些谎言。
如何编写发送的“用于监控字节的传递流”?如果它有任何不同,我正在尝试这样做以将图像从浏览器上传到Cloudinary。
注意:我不对Cloudinary JS library 感兴趣,因为它依赖于 jQuery 而我的应用程序不。我只对使用原生 javascript 和 Github 的 fetch
polyfill 执行此操作所需的流处理感兴趣。
https://fetch.spec.whatwg.org/#fetch-api
【问题讨论】:
@Magix 见Aborting a fetch: The Next Generation #447 @guest271314 上面的链接再次用于在 HTTP 响应中使用流,而不是请求。 非常失望地看到 4 年后仍然没有使用fetch
API 的解决方案:fetch.spec.whatwg.org/#fetch-api it is currently lacking when it comes to request progression (not response progression)
现代浏览器,无 IE:developer.mozilla.org/en-US/docs/Web/API/ReadableStream
【参考方案1】:
获取:还不可能
听起来上传进度最终一旦支持ReadableStream 作为body
就可以使用 fetch。这是currently not implemented,但正在进行中。我认为代码看起来像这样:
警告:此代码尚未生效,仍在等待浏览器支持它
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
可以使用XMLHttpRequest 代替fetch()
来跟踪上传进度——xhr.upload
对象发出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>
【讨论】:
developer.mozilla.org/en-US/docs/Web/API/ReadableStream 可能是请求正文或响应正文,或两者兼而有之? 如果您运行上面的 XHR 示例代码,您会看到它适用于请求和响应正文进度。这些是 XMLHttpRequest 上的独立事件侦听器。对于fetch()
,response.body
是一个可用于跟踪下载进度的流。【参考方案2】:
我的解决方案是使用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.做出反应的例子
【讨论】:
这也是我的解决方案。 Axios 似乎非常适合这种模式。axios
是否在后台使用 fetch
或 XMLHttpRequest
?
XMLHttpRequest。如果您将其用于本机反应,请注意 XMLHttpRequest 与 fetch 相比,解析大型 json 响应似乎非常慢(大约慢 10 倍,并且冻结了整个 ui 线程)。
不回答问题!如果问题是“你如何在 y 中做 x?”说“do x in z”不是一个可以接受的答案。
这并不能回答问题,特别是因为axios
在后台不使用fetch
,也没有这样的支持。我现在真的是在为他们为创作它。【参考方案3】:
更新:正如公认的答案所说,现在不可能。但下面的代码处理了我们的问题一段时间。我应该补充一点,至少我们必须改用基于 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/
【讨论】:
很好,但它也适用于上传吗? @kernel 我试图找出答案,但我无法做到。我也想找到一种上传方式。content-length
!== 正文长度。使用http压缩时(大下载常见),content-length为http压缩后的大小,length为文件解压后的大小。
您的代码假定内容标头长度指定了获取将下载的字节数。这并不总是正确的,因此您的代码无法向用户显示进度,因为 bytesReceived
变得大于 total
此外,甚至浏览器也不事先知道实际的内容长度。您将得到的只是一个压缩后进度指示器。例如,如果您正在下载压缩比分布不均匀的 zip 文件(有些文件是随机的,有些是低熵的),您会注意到进度指示器严重倾斜。【参考方案4】:
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%`;
【讨论】:
我想把整个答案归功于 Benjamin Gruenbaum。因为我是从他的讲座中学到的。 @LeonGilyadov 讲座可以在任何地方在线获得吗?一个指向源的链接会很好。 @MarkAmery 在这里:youtube.com/watch?v=Ja8GKkxahCo(讲座以希伯来语进行) 问题是关于上传,而不是下载。 获取进度的问题是你想上传的时候(下载没有问题)【参考方案5】:关键部分是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 它从我这里得到-1,因为它与上传无关。 我相信它得到了-1,因为它似乎正在编写缩小版 javascript【参考方案6】:因为没有一个答案能解决问题。
为了实现,你可以检测上传速度with some small initial chunk of known size,上传时间可以用content-length/upload-speed计算。您可以将此时间用作估算值。
【讨论】:
非常聪明,在我们等待实时解决方案时使用的好技巧 :) 对我来说太冒险了。不想end up like the windows copy file progress bar 不可靠、复杂并且会显示不正确的值。【参考方案7】:一种可能的解决方法是使用new Request()
构造函数,然后检查Request.bodyUsed
Boolean
属性
bodyUsed
属性的 getter 必须在disturbed
时返回 true,并且 否则为假。
判断流是否为distributed
如果实现
Body
mixin 的对象被称为disturbed
body
不为空,其stream
为disturbed
。
当Request.bodyUsed
等于true
时,从.then()
中返回.then()
中的.read()
递归调用ReadableStream
中的fetch()
Promise
。
注意,该方法不会读取 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));
【讨论】:
这完全没有结果。它只是一个非常复杂的语法,用于显示进度/微调器并在请求完成时隐藏它。【参考方案8】:流开始登陆网络平台 (https://jakearchibald.com/2016/streams-ftw/),但仍处于早期阶段。
很快您就可以提供一个流作为请求的主体,但悬而未决的问题是该流的消耗是否与上传的字节有关。
特定的重定向可能导致数据被重新传输到新位置,但流不能“重新启动”。我们可以通过将主体转换为可以多次调用的回调来解决这个问题,但我们需要确保暴露重定向的数量不是安全漏洞,因为这将是平台上的第一次 JS 可以检测到。
有些人质疑将流消耗与上传的字节相关联是否有意义。
长话短说:这还不可能,但将来这将由流处理,或者传递给fetch()
的某种更高级别的回调。
【讨论】:
太糟糕了。现在接受这个,但是当这成为现实时,我希望其他人会发布更新的解决方案! :) @EitanPeer 不错。类似的东西是否适用于上传,例如发布? @EitanPeer 但是,问题是关于上传进度,而不是下载进度 现在是2020年了,怎么还没有办法呢:( 2021 年了,还是什么都没有?【参考方案9】:我认为这是不可能的。草案规定:
在请求进度方面,它目前缺乏 [与 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
是一个Stream,您可以使用Reader 逐字节读取它,您可以触发一个事件或做任何您喜欢的事情(例如记录进步)为他们每个人。
但是,Streams spec 似乎还没有完成,我不知道这是否已经在任何 fetch 实现中有效。
【讨论】:
如果我没看错的话,这将是通过fetch
下载一个文件。我对上传文件的进度指示器感兴趣。
哎呀,那句话说的是接收字节,这让我很困惑。
@Bergi 注意,Promise
构造函数不是必需的。 Response.body.getReader()
返回 Promise
。见How to solve Uncaught RangeError when download large size json
@guest271314 是的,I've fixed it at the source 已经引用了。不,getReader
不返回承诺。不知道这与您链接的帖子有什么关系。
@Bergi 是的,你是正确的.getReader()
的.read()
方法返回Promise
。这就是试图传达的。该链接暗示了一个前提,即如果可以检查进度以进行下载,则可以检查进度以进行上传。将可在一定程度上返回预期结果的模式组合在一起;这是fetch()
上传的进度。在 jsfiddle 上还没有找到 echo
Blob
或 File
对象的方法,可能缺少一些简单的东西。在localhost
进行的测试可以非常快速地上传文件,而不会模仿网络条件;虽然只记得Network throttling
。以上是关于上传进度指示器以获取?的主要内容,如果未能解决你的问题,请参考以下文章
如何在给定的分钟内以编程方式更新圆形百分比指示器的进度颜色?