Search code examples
javascriptexpressfetch-api

How to get Fetch API promise to resolve as soon as possible?


TL;DR: I expected the fetch API to resolve once the full HTTP response header was sent, but it is taking 8000ms for some reason.

I have a server using Express to respond to HTTP requests. It doesn't give a single response, but rather it makes multiple write calls over time to give status updates. In the browser, the HTTP request is made using the fetch API. The response body is iterated over asynchronously.

All of this works fine except for one issue. The time it takes to await the response from the fetch call is about 8 seconds. I know that the first chunk sent by the server does not take 8 seconds. I have even compared to XHR, which is taking less than 50ms for the progress event to fire. Is there anything I can do to make the initial fetch promise resolve sooner?

Server Code

app.post("/bulk-update", (req, res) => {
  buildTask(req.body, 5, (status) => {
    const completed = status.success + status.error;
    const remaining = status.total - completed;

    if (remaining === 0) {
      res.write(`${JSON.stringify(status)}\n`);
      res.end();
      return;
    }

    if (completed % 10 === 0) {
      res.write(`${JSON.stringify(status)}\n`);
    }
  });
});

Browser Code


let jobStartTime = Date.now();
const response = await fetch(`/bulk-update`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body,
});
console.log("end request", Date.now() - jobStartTime); // <-- this is logging 8000ms

const stream = response.body.pipeThrough(new TextDecoderStream());

let buffer = "";
for await (const chunk of stream) {
  buffer += chunk;

  const lines = buffer.split("\n");
  buffer = lines.at(-1);
  for (let i = 0; i < lines.length - 1; i++) {
    const line = lines[i];
    const status = JSON.parse(line);
    updateProgress(status);
  }

  if (buffer.trim().length > 0) {
    const status = JSON.parse(buffer);
    updateProgress(status);
  }
}

Solution

  • I was able to get the fetch to resolve sooner by setting the Content-Type header to text/event-stream in the response from the server. It seems that if the fetch does not see this header, it takes longer to resolve.

    app.post("/bulk-update", (req, res) => {
      res.setHeader('Content-Type', 'text/event-stream'); // <-- this is the line the fixed it
      res.setHeader('Connection', 'keep-alive'); // Not needed for quick fetch, just is accurate
    
      buildTask(req.body, 5, (status) => {
        const completed = status.success + status.error;
        const remaining = status.total - completed;
    
        if (remaining === 0) {
          res.write(`${JSON.stringify(status)}\n`);
          res.end();
          return;
        }
    
        if (completed % 10 === 0) {
          res.write(`${JSON.stringify(status)}\n`);
        }
      });
    });
    

    I didn't see anything in the Fetch Standard about this. I also tested on Chrome and fetch resolved quickly without needing the header. As far as I can tell, this is an implementation detail specifically with Firefox.