当我在应用程序(前端)中调用函数时,我基本上想这样做:
UI 基本上会经历以下顺序:
所有这些都来自调用一个函数。
该功能将使用
XMLHttpRequest
上传进度功能,如此处。然后,它将使用 fetch
在后端进行轮询以获取作业状态。最后,当作业返回“完成”时,它将获取并返回转换后的文件。
使用基于承诺(非事件发射器)的方法来做到这一点的正确方法是什么?发电机?
async function *performUploadJob() {
const workId = yield await upload(getFile())
yield { type: 'processing' }
const output = await pollForJobComplete(workId)
yield { type: 'result', output }
}
async function pollForJobComplete(workId) {
while (true) {
const res = await fetch(`/work/${workId}`)
const json = await res.json()
if (json.status === 'complete') {
return json.output
}
await wait(2000)
}
}
function *upload(file) {
var fd = new FormData();
fd.append("file", file);
var xhr = new XMLHttpRequest();
xhr.open("POST", "/upload", true);
xhr.upload.onprogress = function(e) {
var percentComplete = Math.ceil((e.loaded / e.total) * 100);
yield { type: 'update', percentComplete }
};
xhr.onload = function() {
if(this.status == 200) {
yield { type: 'update', percentComplete: 100 }
}
}
xhr.send(fd);
}
类似的事情可能吗(伪代码)?
如果是这样,您将如何构建它?如果没有,你会做什么?
目标是能够做这样的事情:
const iterator = performUploadJob()
for (const data of iterator) {
switch (data.type) {
...
}
}
是的,这是可能的,但是我不会推荐它,因为异步迭代器不适合事件发射器。即使您确实使用了
AsyncIterator<ProgressEvent, Response, void>
,使用起来也相当不经济,因为使用 for await … of
循环您不会得到 Response
结果。
对于
pollJob
,异步迭代器就可以了,因为a)你不关心结果(完成后它就会停止)和b)通过轮询,你永远不会比消费者更快。您可以使用类似于您所做的异步生成器来实现此操作:
async function* pollForJob(workId) {
while (true) {
const res = await fetch(`/work/${workId}`)
if (!res.ok) throw new Error('Failed to poll'); // or ignore and carry on
yield res.json()
await wait(2000)
}
}
…
for await (const of pollForJob(upload.jobId)) {
if (json.status === 'complete') {
break;
} else if (json.status === 'running') {
console.log('job continues for', json.estimatedFinish - Date.now());
}
}
console.log('job has finished');
…
如果最终的 poll 实际上返回了处理的结果,那么图片看起来会有所不同。
对于
upload
,我建议实现一个微型“事件循环”,其中每个进度事件都调用提供的事件处理程序。当上传结束时,事件循环终止,并且响应的承诺得到解决(或者当出现错误时,或者上传被中止时)。
function upload(file, onProgress, abortSignal) {
return new Promise((resolve, reject) => {
abortSignal?.throwIfAborted();
var fd = new FormData();
fd.append("file", file);
var xhr = new XMLHttpRequest();
xhr.open("POST", "/upload", true);
const stop = done => value => {
abortSignal?.removeEventListener("abort", fail);
if (xhr.readyState != 4) xhr.abort();
reject(err);
}
const fail = stop(reject);
abortSignals?.addEventListener("abort", fail);
xhr.ontimeout = fail;
xhr.onerror = fail;
xhr.onload = stop(resolve);
xhr.onprogress = e => {
try {
onProgress(e);
} catch(err) {
fail(err);
}
};
xhr.send(fd);
});
}
…
await upload(file, e => {
var percentComplete = Math.ceil((e.loaded / e.total) * 100);
console.log('completion', percentComplete);
});