-
Notifications
You must be signed in to change notification settings - Fork 168
Description
Hi,
This is feedback on this API, after spending the day partially implementing it. It may be way too late for this kind of feedback, but I thought it would be better to post my thoughts than not post. Feel free to ignore me.
I've spent a lot of time in the past thinking about streams and the different interfaces they can have (e.g. when working on Protobufs and Cap'n Proto which care about this kind of thing). I'm impressed that this API seems to cover a lot of obscure cases that people often don't think about, e.g. push vs. pull, backpressure, producer-allocated vs. consumer-allocated buffers, etc.
However, the result feels overcomplicated, and I wonder if it could be simplified.
I wonder if the concept of a "controller" is redundant. I note that ReadableStream's "controller" is almost just a WritableStream, and I wonder if these could actually be unified.
Currently, ReadableStream's constructor takes an underlyingSource
with three (optional) methods: start
, pull
, and cancel
. What if instead of start
and pull
, it just provided pipeTo
, with the same signature as ReadableStream#pipeTo
?
The ReadableStream would not call this callback until some data is first requested. At that time, it can do different things depending on the request:
-
If
getReader()
is called, then the ReadableStream constructs a WritableStream that places chunks in a queue, which theReader
then pulls from. This WritableStream's write() method would return a promise which resolves when the queue is ready for more chunks (as evaluated using the queuing strategy, etc.), to provide backpressure. Having constructed this WritableStream, it is passed to thepipeTo
callback that was given to ReadableStream's constructor. -
On the other hand, if the ReadableStream's own
pipeTo()
method is called instead ofgetReader()
, it can trivially call thepipeTo
callback passing along the same target stream. The ReadableStream can then step out of the picture entirely! This is really nice because it avoids double-buffering, and it gives the original producer the opportunity to query the output stream withinstanceof
, possibly discovering a more-efficient way to implement the pipeline (a common thing to want to do, in my experience!).
The major thing missing from this approach is support for BYOB. I think this could be answered by just throwing byobRequest()
onto WritableStream itself. Or, perhaps a bit more intuitively, the WritableStream interface could be extended with a method like nextBuffer()
which returns a buffer to use, or null if the WritableStream doesn't care. The producer would then be expected to pass that buffer back into write()
-- although they'd also have the option to ignore it and call write()
directly. The implementation of ReadableStreamBYOBReader
would detect this and perform the copy if necessary.
Thoughts?