Description
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}