I want to basically do this when I call a function in my app (frontend):
- Upload a file. Update progress percent in UI.
- Create job and return "Job started" in UI.
- Poll job and wait for it to finish.
- Return response (converted file in this case).
The UI will basically go through this sequence:
- Uploading... (with percentage circle updating)
- Queued...
- Processing...
- Complete
All from calling one function.
The function will use XMLHttpRequest upload progress functionality, like here. It will then poll on the backend using fetch to get the job status. Finally when the job returns "complete", it will fetch and return the converted file.
What is a proper way of doing that with a promise-based (non event-emitter) approach? Generators?
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);
}
Is something like that possible (pseudocode)?
If so, how would you structure it? If not, what would you do instead?
The goal is to be able to just do something like this:
const iterator = performUploadJob()
for (const data of iterator) {
switch (data.type) {
...
}
}
Yes, this is possible, but I wouldn't recommend it because async iterators are a bad fit for event emitters. Even if you did use
AsyncIterator<ProgressEvent, Response, void>, it would be rather uneconomic to use, since with afor await … ofloop you don't get theResponseresult.for
pollJob, an async iterator would be ok, since a) you don't care abou the result (it just stops when done) and b) with polling, you never go faster than the consumer. You can implement this with an async generator similar to what you did:If the final poll actually returns the result of the processing, the picture would look different.
for
upload, I would recommend to implement a miniature "event loop" where each progress event calls a provided event handler. The event loop terminates when the upload ends, and the promise with the response is resolved (or when there's an error, or if the upload is aborted).