Skip to content

Response streams are not being set to an error when a network response ends in an error #27

Open
@etler

Description

@etler

The ReadableStreamDefaultReader instance returned by Response.prototype.body.getReader is not being terminated with an error when a network error happens.

According to the Streams whatwg spec, the reader returned by body should throw an error on await reader.read in the case that the stream results in an error state:

If the stream becomes errored, the promise will be rejected with the relevant error.

In the __didCompleteNetworkResponse method implementation the stream controller is closed with this._streamController?.close(); in the error response states. The correct behavior should call this._streamController?.error(); instead in all the cases where the promise is rejected.

I've tested the behavior in chrome using a minimal streaming server as a test:

const { Readable } = require("node:stream");
const { setTimeout } = require("node:timers/promises");
const Koa = require("koa");

const port = process.env.PORT ?? 3000;

const app = new Koa();

app.use(async (ctx) => {
  const responseStream = new Readable({ read() {} });
  ctx.type = "text/plain";
  ctx.body = responseStream;
  (async () => {
    for (let i = 0; i < 10; i++) {
      await setTimeout(1000);
      console.log(`Pushing ${i}`);
      responseStream.push(`${i}`);
    }
    responseStream.push(null);
  })();
});

app.listen(port);

console.info(`Server started on port ${port}`);

I started a fetch request in a chrome devtools panel using this code:

response = await fetch("http://localhost:3000/")
stream = response.body.pipeThrough(new TextDecoderStream())
reader = stream.getReader()

After starting the request, I let the server progress through several chunks then killed the server. I then called await reader.read() and received a result of {done: false, value: '0'}. The subsequent read after that threw a TypeError: Uncaught TypeError: network error. Despite having sent multiple chunks from the server before killing the server, only the first chunk was buffered, and the subsequent call to read resulted in an error.

If I were to let the server complete through all 10 chunks, each read call would read a single chunk at a time from the buffer even though the full stream was already complete.

Testing the sam behavior with this polyfill library in react native, the read calls do not reject with an error, and instead indicates that the stream is closed and completed successfully returning {done: true, value: undefined}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions