上传进度指示器以获取?
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 执行此操作所需的流处理感兴趣。
我认为这不可能。草案规定:
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计算。您可以将此时间作为估算值。
关键部分是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/ 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');
}
获取:还不可能
听起来上传进度 最终 可以使用 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>
我正在努力寻找使用 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 ifContent-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 执行此操作所需的流处理感兴趣。
我认为这不可能。草案规定:
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 ifdisturbed
, and false otherwise.
判断流是否为distributed
An object implementing the
Body
mixin is said to bedisturbed
ifbody
is non-null and itsstream
isdisturbed
.
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计算。您可以将此时间作为估算值。
关键部分是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/ 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');
}
获取:还不可能
听起来上传进度 最终 可以使用 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>