I’m building a progress bar for some long-running server-side tasks (up to a few minutes), and I’d like a way to display the progress of the task. I could use WebSockets or poll on intervals, but I don’t want to keep track of each task. Instead, I’d like to use long-polling and write progress updates to the stream.
Here is a demo of what the route should look like on the server
app.get('/test', (req, res) => { let num = 0; const interval = setInterval(() => res.write(num++ + ' '), 300); setTimeout(() => { clearInterval(interval); res.send(); }, 5000); });
Doing cURL on that endpoint with -N
works perfectly, however, I’m having some issues when it comes to implementing this in the browser.
I tried with fetch like this:
const response = await fetch(url); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; console.log(decoder.decode(value)); }
This worked just dandy on Chrome, but not on firefox and as you can see, it’s not supported here:
https://caniuse.com/mdn-api_windoworworkerglobalscope_fetch_streaming_response_body
However, I tried a different approach, this time with XHR
const xhr = new XMLHttpRequest() xhr.open("GET", url) xhr.onprogress = function () { console.log(xhr.responseText); }; xhr.send();
This works perfectly in Firefox, but in Chrome, the onProgress event only fires after the entire request has been processed. I’ve also tried with onReadyStateChange
, but that results in the same problem.
>_< How do I read this gosh darn data in chunks as it updates in either browser? I guess I could try Axios, but do I really need this?
EDIT: One thing it might be worth mentoining is that Chrome and Firefox seem to handle the fetch behavior differently. With Chrome, I can work with the fetch object before the fetch completes, so I do
const response = await fetch(url); console.log("Preflight complete, fetch is not done though");
but in Firefox, the console log won’t execute until the fetch resolves. This is why I think I can’t work with the response body in Firefox, but I can with Chrome.
Advertisement
Answer
According to this GitHub issue:
https://github.com/ratpack/ratpack/issues/443#issuecomment-59621215
This is a Chrome/Webkit bug. Changing the Content-Type
of the request from anything other than text/plain
makes it work with XHR
on Chrome. So if I change the server response to
app.get('/test', (req, res) => { let num = 0; let interval = setInterval(() => res.write(num++ + ' '), 300); // THIS IS A DUMB HACK TO GET CHROME TO PLAY NICE X_X res.setHeader('Content-Type', 'text/html'); setTimeout(() => { clearInterval(interval); res.send(); }, 5000); });
SHOCKINGLY, this also seems to fix the issue with fetch streaming in Firefox with unmodified flags. I think I’ll go with the XHR
method for now, just because it’s a bit more compaitble, HOWEVER, the fetch version is significanlty easier to work with given each new chunk is processed individually.