Skip to content
Advertisement

Get a response in chunks from express in browser

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.

AHHHHHHHHH

Advertisement