You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Right now, piping to a writable stream with { highWaterMark: 0 } stalls indefinitely:
constrs=newReadableStream({start(c){c.enqueue("a");c.enqueue("b");c.enqueue("c");c.close();}});constws=newWritableStream({write(chunk){console.log("wrote:",chunk);}},{highWaterMark: 0});rs.pipeTo(ws);// never resolves, and no messages are logged
This makes it impossible to pipe through a TransformStream without increasing the total queue size of a pipe chain by at least one chunk:
constupperCaseTransform=newTransformStream({transform(chunk,controller){controller.enqueue(chunk.toUpperCase());}},{highWaterMark: 0},{highWaterMark: 0});rs.pipeThrough(upperCaseTransform).pipeTo(ws);// stalls indefinitelyconstupperCaseTransform=newTransformStream({transform(chunk,controller){controller.enqueue(chunk.toUpperCase());}},{highWaterMark: 1},{highWaterMark: 0});// same as default strategiesrs.pipeThrough(upperCaseTransform);// works, but already pulls the first chunk from `rs`
This is unfortunate, since there are many use cases for synchronous TransformStreams that shouldn't need buffering (i.e. every call to transform() immediately results in at least one enqueue()):
A generic mapTransform(fn), similar to array.map(fn):
Yes. As you observed, a writable stream with a HWM of 0 will always have backpressure. So adding an identity TransformStream to a pipe can't be a complete no-op: it always increases the total queue size by 1.
But that got me thinking. A ReadableStream's source can be pull()ed as a result of reader.read(), even if controller.desiredSize <= 0. Maybe a WritableStream's sink should then also be able to release backpressure even if writer.desiredSize <= 0? 🤔
We could add a method on WritableStreamDefaultController (controller.pull()? controller.releaseBackpressure()? controller.notifyReady()?) that would have the result of immediately resolving the current writer.ready promise. Internally, we would do something like WritableStreamUpdateBackpressure(stream, false). My hope is that we can then use this inside TransformStreamSetBackpressure(), so that pulling from the readable end of a transform stream would also resolve ready on the writable end.
Right now, piping to a writable stream with
{ highWaterMark: 0 }stalls indefinitely:This makes it impossible to pipe through a
TransformStreamwithout increasing the total queue size of a pipe chain by at least one chunk:This is unfortunate, since there are many use cases for synchronous
TransformStreams that shouldn't need buffering (i.e. every call totransform()immediately results in at least oneenqueue()):mapTransform(fn), similar toarray.map(fn):TextEncoderStreamandTextDecoderStreamfrom Encoding.Prior discussions on this topic noted that this is not possible.
writer.desiredSizeis always<= 0, sowriter.readyis always pending:But that got me thinking. A
ReadableStream's source can bepull()ed as a result ofreader.read(), even ifcontroller.desiredSize <= 0. Maybe aWritableStream's sink should then also be able to release backpressure even ifwriter.desiredSize <= 0? 🤔We could add a method on
WritableStreamDefaultController(controller.pull()?controller.releaseBackpressure()?controller.notifyReady()?) that would have the result of immediately resolving the currentwriter.readypromise. Internally, we would do something likeWritableStreamUpdateBackpressure(stream, false). My hope is that we can then use this insideTransformStreamSetBackpressure(), so that pulling from the readable end of a transform stream would also resolvereadyon the writable end....Or am I missing something very obvious? 😛