Skip to content

Commit 014c78f

Browse files
authored
Add backpressure information to tee, clone (#16804)
* Add backpressure information to tee, clone Add information on what causes backpressure to ReadableStream.tee, Request.clone, Response.clone. * pr feedback: grammar * Elaborate on tee/clone backpressure * respond to pr feedback. Use _x_ instead of *x* for emphasis
1 parent a543291 commit 014c78f

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

files/en-us/web/api/readablestream/tee/index.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,29 @@ The **`tee()`** method of the
1818
two-element array containing the two resulting branches as
1919
new {{domxref("ReadableStream")}} instances.
2020

21-
This is useful for allowing two readers to read a stream simultaneously, perhaps at
22-
different speeds. You might do this for example in a ServiceWorker if you want to fetch
21+
This is useful for allowing two readers to read a stream sequentially or simultaneously,
22+
perhaps at different speeds.
23+
For example, you might do this in a ServiceWorker if you want to fetch
2324
a response from the server and stream it to the browser, but also stream it to the
2425
ServiceWorker cache. Since a response body cannot be consumed more than once, you'd need
2526
two copies to do this.
2627

28+
A teed stream will partially signal backpressure at the rate of the _faster_ consumer
29+
of the two `ReadableStream` branches,
30+
and unread data is enqueued internally on the slower consumed `ReadableStream`
31+
without any limit or backpressure.
32+
That is, when _both_ branches have an unread element in their internal queue,
33+
then the original `ReadableStream`’s controller’s internal queue will start to fill up,
34+
and once its {{domxref("ReadableStreamDefaultController.desiredSize", "desiredSize")}} ≤ 0
35+
or byte stream controller {{domxref("ReadableByteStreamController.desiredSize", "desiredSize")}} ≤ 0,
36+
then the controller will stop calling `pull(controller)` on the
37+
underlying source passed to {{domxref("ReadableStream.ReadableStream", "new ReadableStream()")}}.
38+
If only one branch is consumed, then the entire body will be enqueued in memory.
39+
Therefore, you should not use the built-in `tee()` to read very large streams
40+
in parallel at different speeds.
41+
Instead, search for an implementation that fully backpressures
42+
to the speed of the _slower_ consumed branch.
43+
2744
To cancel the stream you then need to cancel both resulting branches. Teeing a stream
2845
will generally lock it for the duration, preventing other readers from locking it.
2946

files/en-us/web/api/request/clone/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ browser-compat: api.Request.clone
1515

1616
The **`clone()`** method of the {{domxref("Request")}} interface creates a copy of the current `Request` object.
1717

18+
Like the underlying {{domxref("ReadableStream.tee")}} api,
19+
the {{domxref("Request.body", "body")}} of a cloned `Response`
20+
will signal backpressure at the rate of the _faster_ consumer of the two bodies,
21+
and unread data is enqueued internally on the slower consumed `body`
22+
without any limit or backpressure.
23+
Beware when you construct a `Request` from a stream and then `clone` it.
24+
1825
`clone()` throws a {{jsxref("TypeError")}} if the request body has already been used. In fact, the main reason `clone()` exists is to allow multiple uses of body objects (when they are one-use only.)
1926

2027
If you intend to modify the request, you may prefer the {{domxref("Request")}} constructor.

files/en-us/web/api/response/clone/index.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ browser-compat: api.Response.clone
1515

1616
The **`clone()`** method of the {{domxref("Response")}} interface creates a clone of a response object, identical in every way, but stored in a different variable.
1717

18+
Like the underlying {{domxref("ReadableStream.tee")}} api,
19+
the {{domxref("Response.body", "body")}} of a cloned `Response`
20+
will signal backpressure at the rate of the _faster_ consumer of the two bodies,
21+
and unread data is enqueued internally on the slower consumed `body`
22+
without any limit or backpressure.
23+
Backpressure refers to the mechanism by which the streaming consumer of data
24+
(in this case, the code that reads the body)
25+
slows down the producer of data (such as the TCP server)
26+
so as not to load large amounts of data in memory
27+
that is waiting to be used by the application.
28+
If only one cloned branch is consumed, then the entire body will be buffered in memory.
29+
Therefore, `clone()` is one way to read a response twice in sequence,
30+
but you should not use it to read very large bodies
31+
in parallel at different speeds.
32+
1833
`clone()` throws a {{jsxref("TypeError")}} if the response body has already been used.
1934
In fact, the main reason `clone()` exists is to allow multiple uses of body objects (when they are one-use only.)
2035

0 commit comments

Comments
 (0)