上传获取的进度指示器?

我正在努力寻找使用 捡回来实现上传进度指示器的文档或示例。

这是到目前为止我找到的唯一一个引用 ,它说:

进程事件是一个高级特性,目前还不能提取。您可以通过查看 Content-Length头并使用直通流来监视接收到的字节来创建自己的。

这意味着您可以在不使用 Content-Length的情况下显式地处理响应。当然,即使 Content-Length在那里,它也可能是一个谎言。有了数据流,你可以随心所欲地处理这些谎言。

如何编写发送的“监视字节的传递流”?如果有什么不同的话,我正在尝试这样做,以便将图像从浏览器上传到 迷雾

注意 : 我对 Cloudinary JS 库感兴趣,因为它依赖于 jQuery,而我的应用程序不依赖于它。我只对使用本地 javascript 和 Github 的 fetch polyfill 完成这项工作所需的流处理感兴趣。


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

129132 次浏览

我觉得不可能,草案上说:

它目前缺乏[ 和 XHR 相比]当谈到请求进展


(旧答案) :
获取 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是一个记录流,你可以使用一个读取器一个字节一个字节地读取它,你可以触发一个事件或者为每个事件做任何你想做的事情(例如记录进度)。

然而,流规格似乎还没有完全完成,我不知道这是否已经在任何提取实现中起作用。

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

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

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

一些人甚至质疑将流消耗与上传的字节联系起来是否有意义。

长话短说: 这是不可能的,但在未来,这将处理流,或某种更高级别的回调传递到 fetch()

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

如果 disturbed和 否则就是错的。

确定流是否为 distributed

实现 Body混合的对象称为 disturbed,如果 body是非空的,它的 streamdisturbed

Request.bodyUsed等于 true时,从 .then()链接到 ReadableStream的递归 .read()调用中返回 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));


}

因为没有一个答案能解决这个问题。

只是为了实现起见,你可以检测上传速度 一些已知大小的初始小块和上传时间可以计算内容长度/上传速度。你可以用这段时间做个估计。

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

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.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');
}

感谢这个链接: https://jakearchibald.com/2016/streams-ftw/

还不可能

这听起来像上传进度 最终会的是可能的取一旦它支持一个 ReadableStream作为 body。这是 目前尚未实施但还在进行中。我认为代码应该是这样的:

警告: 此代码尚未工作,仍在等待浏览器支持它

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" />

workaround: good ol' XMLHttpRequest

Instead of fetch(), it's possible to use 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是不可能的,但使用 XHR 是不可能的。以下是我的更小巧一点的 XHR 解决方案:

const uploadFiles = (url, files, onProgress) =>
new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', e => onProgress(e.loaded / e.total));
xhr.addEventListener('load', () => resolve({ status: xhr.status, body: xhr.responseText }));
xhr.addEventListener('error', () => reject(new Error('File upload failed')));
xhr.addEventListener('abort', () => reject(new Error('File upload aborted')));
xhr.open('POST', url, true);
const formData = new FormData();
Array.from(files).forEach((file, index) => formData.append(index.toString(), file));
xhr.send(formData);
});

使用一个或多个文件。

如果您有这样的文件输入元素:

<input type="file" multiple id="fileUpload" />

这样调用函数:

document.getElementById('fileUpload').addEventListener('change', async e => {
const onProgress = progress => console.log('Progress:', `${Math.round(progress * 100)}%`);
const response = await uploadFiles('/api/upload', e.currentTarget.files, onProgress);
if (response.status >= 400) {
throw new Error(`File upload failed - Status code: ${response.status}`);
}
console.log('Response:', response.body);
}

在构建文件放置区域时,还可以使用从 drop事件获得的 e.dataTransfer.files

关于这个问题,我已经摸索了一段时间,对于每一个可能遇到这个问题的人,这里是我的解决方案:

const form = document.querySelector('form');
const status = document.querySelector('#status');


// When form get's submitted.
form.addEventListener('submit', async function (event) {
// cancel default behavior (form submit)
event.preventDefault();


// Inform user that the upload has began
status.innerText = 'Uploading..';


// Create FormData from form
const formData = new FormData(form);


// Open request to origin
const request = await fetch('https://httpbin.org/post', { method: 'POST', body: formData });


// Get amount of bytes we're about to transmit
const bytesToUpload = request.headers.get('content-length');


// Create a reader from the request body
const reader = request.body.getReader();


// Cache how much data we already send
let bytesUploaded = 0;


// Get first chunk of the request reader
let chunk = await reader.read();


// While we have more chunks to go
while (!chunk.done) {
// Increase amount of bytes transmitted.
bytesUploaded += chunk.value.length;


// Inform user how far we are
status.innerText = 'Uploading (' + (bytesUploaded / bytesToUpload * 100).toFixed(2) + ')...';


// Read next chunk
chunk = await reader.read();
}
});

附加值: 现在可以使用 Chrome > = 105

如何: Https://developer.chrome.com/articles/fetch-streaming-requests/

目前不支持其他浏览器(也许当您阅读本文时会出现这种情况,请相应地编辑我的答案)

特征提取(来源)

const supportsRequestStreams = (() => {
let duplexAccessed = false;


const hasContentType = new Request('', {
body: new ReadableStream(),
method: 'POST',
get duplex() {
duplexAccessed = true;
return 'half';
},
}).headers.has('Content-Type');


return duplexAccessed && !hasContentType;
})();

需要 HTTP > = 2

如果连接是 HTTP/1.x,那么提取操作将被拒绝。