Description
Spinning off from roughly yutakahirano/fetch-with-streams#30 (comment), I think we want to take a more general look at how we envision transform streams working.
Here is some code for reference:
const ts = TransformStream.identity({ highWaterMark: 0 }); // speculative API
ts.writable.write("foo").then(onWritten, onWriteFailed);
setTimeout(() => {
ts.readable.getReader().read().then(({ value, done }) => {
// (1)
return doSomethingAsyncWith(value);
});
}, 1000);
In the current design, onWritten
will always be called ASAP, whereas onWriteFailed
will never happen. (In the more general transform stream case, onWritten
will be called when the possibly-async transformation finishes, while onWriteFailed
will be called if it fails.) The current design is based on how Node.js/io.js handles things.
In the linked thread, @wanderview proposes (indirectly) a design where onWritten
is called only after the chunk leaves the transform stream to head for the readable stream (i.e. after 1 second, roughly around the same time as (1)). In this design onWriteFailed
is also never called.
@tyoshino's read-acknowledgment proposal (#324 and previous) would allow for a third design, where onWritten
would be called once doSomethingAsyncWith(value)
succeeds, and onWriteFailed
would be called if it fails. This would require a slight modification to the above, e.g. something like
ts.readable.getReader({ requireAcks: true }).read().then(({ value, done, waitUntil }) => {
waitUntil(doSomethingAsyncWith(value));
});
which could be a burden on the consumer to remember to use.
There's two aspects to keep in mind with these proposals:
- How well they propagate write success/failure
- How well they propagate backpressure
I think all can propagate backpressure pretty well, with minor tricks. But propagating write success/failure all the way from the ultimate sink to the original producer is only really possible with #324, I think. (Except perhaps through things like #325 which could help with special cases...)