diff --git a/.editorconfig b/.editorconfig index 874a0b40a..f0b203f28 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,18 +4,19 @@ root = true end_of_line = lf insert_final_newline = true charset = utf-8 -indent_size = 2 +indent_size = 1 indent_style = space trim_trailing_whitespace = true - -[*.{js,bs}] -max_line_length = 120 - -[.gitmodules] -indent_style = tab +max_line_length = 100 [Makefile] indent_style = tab [.travis.yml] indent_size = 2 + +[*.js] +max_line_length = 120 + +[.gitmodules] +indent_style = tab diff --git a/.gitignore b/.gitignore index a9e77d4d4..491ac7aa1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,3 @@ /deploy_key.pub /index.html /review.sh -/index.html.* -/node_modules/ -/npm-debug.log diff --git a/.pr-preview.json b/.pr-preview.json index 4b2477666..03fdd6beb 100644 --- a/.pr-preview.json +++ b/.pr-preview.json @@ -5,11 +5,5 @@ "force": 1, "md-status": "LS-PR", "md-Text-Macro": "PR-NUMBER {{ pull_request.number }}" - }, - "post_processing": { - "name": "emu-algify", - "options": { - "throwingIndicators": true - } } } diff --git a/.travis.yml b/.travis.yml index 9f4f47a56..a525d76bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: node_js node_js: stable -sudo: false env: global: @@ -12,7 +11,6 @@ before_install: script: - npm test - cd .. - - npm install - make deploy branches: diff --git a/Makefile b/Makefile index cf8a4e24a..4443a981f 100644 --- a/Makefile +++ b/Makefile @@ -2,17 +2,14 @@ SHELL=/bin/bash -o pipefail .PHONY: local remote deploy review remote: index.bs - curl https://api.csswg.org/bikeshed/ -f -F file=@index.bs > index.html.postbs -F md-Text-Macro="SNAPSHOT-LINK LOCAL COPY" - node_modules/.bin/emu-algify --throwing-indicators < index.html.postbs > index.html + curl https://api.csswg.org/bikeshed/ -f -F file=@index.bs > index.html -F md-Text-Macro="COMMIT-SHA LOCAL COPY" local: index.bs - bikeshed spec index.bs index.html.postbs --md-Text-Macro="SNAPSHOT-LINK LOCAL COPY" - node_modules/.bin/emu-algify --throwing-indicators < index.html.postbs > index.html + bikeshed spec index.bs index.html --md-Text-Macro="COMMIT-SHA LOCAL COPY" deploy: index.bs curl --remote-name --fail https://resources.whatwg.org/build/deploy.sh EXTRA_FILES="demos/* demos/**/*" \ - POST_BUILD_STEP='node_modules/.bin/emu-algify --throwing-indicators < "$$DIR/index.html" > "$$DIR/index.html.tmp"; mv "$$DIR/index.html.tmp" "$$DIR/index.html"' \ bash ./deploy.sh review: index.bs diff --git a/README.md b/README.md index 6cb27f4a5..56b3577b6 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,6 @@ implementation in order to pass those tests. ## Building "locally" -This standard requires a recent version of [Node.js](https://nodejs.org/en/) to be installed as a -prerequisite. Once that's done, you'll need to do a one-time run of `npm install` to set up our -tooling. - For quick local iteration, run `make`. To verify your changes locally, run `make deploy`. See more in the [WHATWG Contributor Guidelines](https://github.com/whatwg/meta/blob/master/CONTRIBUTING.md#building). diff --git a/index.bs b/index.bs index d83fcd895..5edff586a 100644 --- a/index.bs +++ b/index.bs @@ -7,49 +7,59 @@ Abstract: This specification provides APIs for creating, composing, and consumin Abstract: to low-level I/O primitives. Translation: ja https://triple-underscore.github.io/Streams-ja.html !Demos: streams.spec.whatwg.org/demos -Opaque Elements: emu-alg +Indent: 1 +Markup Shorthands: markdown yes
-spec:webidl; type:dfn; - text:resolve +spec:webidl; type:dfn; text:resolve +spec:webidl; type:dfn; text:new +spec:infra; type:dfn; text:list
-urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT - text: %Uint8Array%; url: #sec-typedarray-objects; type: constructor - text: %AsyncIteratorPrototype%; url: #sec-asynciteratorprototype; type: interface - text: AsyncIterator; url: #sec-asynciterator-interface; type: interface - text: ArrayBuffer; url: #sec-arraybuffer-objects; type: interface - text: DataView; url: #sec-dataview-objects; type: interface - text: Number; url: #sec-ecmascript-language-types-number-type; type: interface - text: Uint8Array; url: #sec-typedarray-objects; type: interface - text: typed array; url: #sec-typedarray-objects; type: dfn - text: the typed array constructors table; url: #table-49; type: dfn - text: TypeError; url: #sec-native-error-types-used-in-this-standard-typeerror; type: exception - text: Invoke; url: #sec-invoke; type: abstract-op - text: DestructuringAssignmentEvaluation; url: #sec-runtime-semantics-destructuringassignmentevaluation; type: abstract-op - text: map; url: #sec-array.prototype.map; type: method; for: Array.prototype - text: CreateIterResultObject; url: #sec-createiterresultobject; type: abstract-op +urlPrefix: https://tc39.es/ecma262/; spec: ECMASCRIPT + type: constructor + text: %Uint8Array%; url: #sec-typedarray-objects + text: %DataView%; url: #sec-dataview-constructor + text: %ArrayBuffer%; url: #sec-arraybuffer-constructor + type: interface + text: %ObjectPrototype%; url: #sec-properties-of-the-object-prototype-object + text: ArrayBuffer; url: #sec-arraybuffer-objects + text: DataView; url: #sec-dataview-objects + text: Number; url: #sec-ecmascript-language-types-number-type + text: Uint8Array; url: #sec-typedarray-objects + type: dfn + text: abstract operation; url: #sec-algorithm-conventions-abstract-operations + text: completion record; url: #sec-completion-record-specification-type + text: internal slot; url: #sec-object-internal-methods-and-internal-slots + text: record; url: #sec-list-and-record-specification-type + text: the current Realm; url: #current-realm + text: the typed array constructors table; url: #table-49 + text: typed array; url: #sec-typedarray-objects + type: abstract-op + text: CloneArrayBuffer; url: #sec-clonearraybuffer + text: CopyDataBlockBytes; url: #sec-copydatablockbytes + text: CreateArrayFromList; url: #sec-createarrayfromlist + text: CreateBuiltinFunction; url: #sec-createbuiltinfunction + text: CreateDataProperty; url: #sec-createdataproperty + text: CreateIterResultObject; url: #sec-createiterresultobject + text: Construct; url: #sec-construct + text: DetachArrayBuffer; url: #sec-detacharraybuffer + text: Get; url: #sec-get + text: GetV; url: #sec-getv + text: IsDetachedBuffer; url: #sec-isdetachedbuffer + text: IsInteger; url: #sec-isinteger + text: OrdinaryObjectCreate; url: #sec-ordinaryobjectcreate + text: SetFunctionLength; url: #sec-setfunctionlength + text: SetFunctionName; url: #sec-setfunctionname + text: Type; url: #sec-ecmascript-data-types-and-values + text: TypeError; url: #sec-native-error-types-used-in-this-standard-typeerror; type: exception + text: map; url: #sec-array.prototype.map; type: method; for: Array.prototype
writable
property and a readable
property can serve as a
-transform stream. However, the standard {{TransformStream}} class makes it much easier to create such a pair that is
-properly entangled. It wraps a transformer, which defines algorithms for the specific transformation to be
-performed. For web developer–created streams, the implementation details of a transformer are provided by an object with certain methods and properties that is passed to the {{TransformStream()}} constructor.
-
-An identity transform stream is a type of transform stream which forwards all chunks written
-to its writable side to its readable side, without any changes. This can be useful in a variety of scenarios. By default, the {{TransformStream}} constructor will
-create an identity transform stream, when no {{transformer/transform()}} method is present on the transformer
-object.
+A transform stream consists of a pair of streams: a [=writable stream=], known as
+its writable side, and a [=readable stream=], known as its readable
+side. In a manner specific to the transform stream in question, writes to the writable side
+result in new data being made available for reading from the readable side.
+
+Concretely, any object with a writable
property and a readable
property
+can serve as a transform stream. However, the standard {{TransformStream}} class makes it much
+easier to create such a pair that is properly entangled. It wraps a transformer, which
+defines algorithms for the specific transformation to be performed. For web developer–created
+streams, the implementation details of a transformer are provided by an
+object with certain methods and properties that is passed to the {{TransformStream()}}
+constructor.
+
+An identity transform stream is a type of transform stream which forwards all
+[=chunks=] written to its [=writable side=] to its [=readable side=], without any changes. This can
+be useful in a variety of scenarios. By default, the
+{{TransformStream}} constructor will create an identity transform stream, when no
+{{Transformer/transform|transform()}} method is present on the [=transformer=] object.
Some examples of potential transform streams include:
-highWaterMark
property. For byte streams the highWaterMark
always has units of bytes. For other streams the default unit is chunks, but a
-size()
function can be included in the strategy object which returns the size
-for a given chunk. This permits the highWaterMark
to be specified in
-arbitrary floating-point units.
+Both readable and writable streams maintain internal queues, which they use for similar
+purposes. In the case of a readable stream, the internal queue contains [=chunks=] that have been
+enqueued by the [=underlying source=], but not yet read by the consumer. In the case of a writable
+stream, the internal queue contains [=chunks=] which have been written to the stream by the
+producer, but not yet processed and acknowledged by the [=underlying sink=].
+
+A queuing strategy is an object that determines how a stream should signal
+[=backpressure=] based on the state of its [=internal queue=]. The queuing strategy assigns a size
+to each [=chunk=], and compares the total size of all chunks in the queue to a specified number,
+known as the high water mark. The resulting difference, high water mark minus total size,
+is used to determine the desired size to
+fill the stream's queue.
+
+For readable streams, an underlying source can use this desired size as a backpressure signal,
+slowing down chunk generation so as to try to keep the desired size above or at zero. For writable
+streams, a producer can behave similarly, avoiding writes that would cause the desired size to go
+negative.
+
+Concretely, a queuing strategy for web developer–created streams is given by
+any JavaScript object with a {{QueuingStrategy/highWaterMark}} property. For byte streams the
+{{QueuingStrategy/highWaterMark}} always has units of bytes. For other streams the default unit is
+[=chunks=], but a {{QueuingStrategy/size|size()}} function can be included in the strategy object
+which returns the size for a given chunk. This permits the {{QueuingStrategy/highWaterMark}} to be
+specified in arbitrary floating-point units.
{ highWaterMark: 3, size() { return 1; }}
,
- or using the built-in {{CountQueuingStrategy}} class, as new CountQueuingStrategy({ highWaterMark: 3 })
.
+ A simple example of a queuing strategy would be one that assigns a size of one to each chunk, and
+ has a high water mark of three. This would mean that up to three chunks could be enqueued in a
+ readable stream, or three chunks written to a writable stream, before the streams are considered to
+ be applying backpressure.
+
+ In JavaScript, such a strategy could be written manually as { highWaterMark:
+ 3, size() { return 1; }}
, or using the built-in {{CountQueuingStrategy}} class, as new CountQueuingStrategy({ highWaterMark: 3 })
.
Under the covers, these high-level operations actually use a reader or writer +themselves.
+ +A given readable or writable stream only has at most one reader or writer at a time. We say in this +case the stream is locked, and that the +reader or writer is active. This state can be +determined using the {{ReadableStream/locked|readableStream.locked}} or +{{WritableStream/locked|writableStream.locked}} properties. + +A reader or writer also has the capability to release its lock, which makes it no longer active, and allows further readers or +writers to be acquired. This is done via the +{{ReadableStreamDefaultReader/releaseLock()|defaultReader.releaseLock()}}, +{{ReadableStreamBYOBReader/releaseLock()|byobReader.releaseLock()}}, or +{{WritableStreamDefaultWriter/releaseLock()|writer.releaseLock()}} method, as appropriate. -A readable byte stream has the ability to vend two types of readers: default readers and BYOB -readers. BYOB ("bring your own buffer") readers allow reading into a developer-supplied buffer, thus minimizing -copies. A non-byte readable stream can only vend default readers. Default readers are instances of the -{{ReadableStreamDefaultReader}} class, while BYOB readers are instances of {{ReadableStreamBYOBReader}}. +Under the covers, these high-level operations actually use a reader or writer themselves.
+This specification uses the [=abstract operation=] concept from [[!ECMASCRIPT]] for its internal +algorithms. This includes treating their return values as [=completion records=], the use of ! and +? prefixes for unwrapping those completion records. -A given readable or writable stream only has at most one reader or writer at a time. We say in this case the stream is -locked, and that the reader or writer is active. This state can be determined using the -{{ReadableStream/locked|readableStream.locked}} or {{WritableStream/locked|writableStream.locked}} properties. +This specification also uses the [=internal slot=] and [=record=] concepts and notation from +ECMAScript. (Although, the internal slots are on Web IDL [=platform objects=] instead of ECMAScript +objects.) -A reader or writer also has the capability to release -its lock, which makes it no longer active, and allows further readers or writers to be acquired. This is done via -the {{ReadableStreamDefaultReader/releaseLock()|defaultReader.releaseLock()}}, -{{ReadableStreamBYOBReader/releaseLock()|byobReader.releaseLock()}}, or -{{WritableStreamDefaultWriter/releaseLock()|writer.releaseLock()}} method, as appropriate. +Finally, as in [[!ECMASCRIPT]], all numbers are represented as double-precision floating point +values, and all arithmetic operations performed on them must be done in the standard way for such +values.
- readableStream.pipeTo(writableStream)
- .then(() => console.log("All data successfully written!"))
- .catch(e => console.error("Something went wrong!", e));
-
+ The simplest way to consume a readable stream is to simply [=piping|pipe=] it to a [=writable
+ stream=]. This ensures that [=backpressure=] is respected, and any errors (either writing or
+ reading) are propagated through the chain:
+
+
- readableStream.pipeTo(new WritableStream({
- write(chunk) {
- console.log("Chunk received", chunk);
- },
- close() {
- console.log("All data successfully read!");
- },
- abort(e) {
- console.error("Something went wrong!", e);
- }
- }));
-
-
- By returning promises from your {{underlying sink/write()}} implementation, you can signal backpressure to the
- readable stream.
+ If you simply want to be alerted of each new chunk from a readable stream, you can [=piping|pipe=]
+ it to a new [=writable stream=] that you custom-create for that purpose:
+
+ read()
method to get successive chunks. For example, this code
- logs the next chunk in the stream, if available:
-
-
- const reader = readableStream.getReader();
-
- reader.read().then(
- ({ value, done }) => {
- if (done) {
- console.log("The stream was already closed!");
- } else {
- console.log(value);
- }
- },
- e => console.error("The stream became errored and cannot be read from!", e)
- );
-
-
- This more manual method of reading a stream is mainly useful for library authors building new high-level operations
- on streams, beyond the provided ones of piping and teeing.
+ Although readable streams will usually be used by piping them to a writable stream, you can also
+ read them directly by acquiring a [=reader=] and using its read()
method to get
+ successive chunks. For example, this code logs the next [=chunk=] in the stream, if available:
+
+
- const reader = readableStream.getReader({ mode: "byob" });
-
- let startingAB = new ArrayBuffer(1024);
- readInto(startingAB)
- .then(buffer => console.log("The first 1024 bytes:", buffer))
- .catch(e => console.error("Something went wrong!", e));
-
- function readInto(buffer, offset = 0) {
- if (offset === buffer.byteLength) {
- return Promise.resolve(buffer);
- }
+ The above example showed using the readable stream's [=default reader=]. If the stream is a
+ [=readable byte stream=], you can also acquire a [=BYOB reader=] for it, which allows more
+ precise control over buffer allocation in order to avoid copies. For example, this code reads the
+ first 1024 bytes from the stream into a single memory buffer:
+
+
+ const reader = readableStream.getReader({ mode: "byob" });
+
+ let startingAB = new ArrayBuffer(1024);
+ readInto(startingAB)
+ .then(buffer => console.log("The first 1024 bytes:", buffer))
+ .catch(e => console.error("Something went wrong!", e));
+
+ function readInto(buffer, offset = 0) {
+ if (offset === buffer.byteLength) {
+ return Promise.resolve(buffer);
+ }
+
+ const view = new Uint8Array(buffer, offset, buffer.byteLength - offset);
+ return reader.read(view).then(newView => {
+ return readInto(newView.buffer, offset + newView.byteLength);
+ });
+ }
+
+
+ An important thing to note here is that the final buffer
value is different from the
+ startingAB
, but it (and all intermediate buffers) shares the same backing memory
+ allocation. At each step, the buffer is transferred to a new
+ {{ArrayBuffer}} object. The newView
is a new {{Uint8Array}}, with that {{ArrayBuffer}}
+ object as its buffer
property, the offset that bytes were written to as its
+ byteOffset
property, and the number of bytes that were written as its
+ byteLength
property.
+
buffer
value is different from the
- startingAB
, but it (and all intermediate buffers) shares the same backing memory allocation. At each
- step, the buffer is transferred to a new {{ArrayBuffer}} object. The
- newView
is a new {{Uint8Array}}, with that {{ArrayBuffer}} object as its buffer
property,
- the offset that bytes were written to as its byteOffset
property, and the number of bytes that were
- written as its byteLength
property.
-
+The {{ReadableStream}} class is a concrete instance of the general [=readable stream=] concept. It
+is adaptable to any [=chunk=] type, and maintains an internal queue to keep track of data supplied
+by the [=underlying source=] but not yet read by any consumer.
-ReadableStream
- class ReadableStream {
- constructor(underlyingSource = {}, strategy = {})
+ // TODO: async iterator
+};
- get locked()
+typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader;
- cancel(reason)
- getIterator({ preventCancel } = {})
- getReader({ mode } = {})
- pipeThrough({ writable, readable },
- { preventClose, preventAbort, preventCancel, signal } = {})
- pipeTo(dest, { preventClose, preventAbort, preventCancel, signal } = {})
- tee()
+enum ReadableStreamReaderMode { "byob" };
- [@@asyncIterator]({ preventCancel } = {})
- }
-
+dictionary ReadableStreamGetReaderOptions {
+ ReadableStreamReaderMode mode;
+};
+
+dictionary ReadableWritablePair {
+ required ReadableStream readable;
+ required WritableStream writable;
+};
+
+dictionary StreamPipeOptions {
+ boolean preventClose = false;
+ boolean preventAbort = false;
+ boolean preventCancel = false;
+ AbortSignal signal;
+};
+Internal Slot | -Description (non-normative) | -
---|---|
\[[disturbed]] - | A boolean flag set to | Internal Slot + | Description (non-normative) + |
\[[readableStreamController]] - | A {{ReadableStreamDefaultController}} or {{ReadableByteStreamController}} created with - the ability to control the state and queue of this stream; also used for the IsReadableStream brand check - | \[[disturbed]] + | A boolean flag set to true when the stream has been read from or + canceled |
\[[reader]] - | A {{ReadableStreamDefaultReader}} or {{ReadableStreamBYOBReader}} instance, if the stream
- is locked to a reader, or | \[[readableStreamController]] + | A {{ReadableStreamDefaultController}} or + {{ReadableByteStreamController}} created with the ability to control the state and queue of this + stream |
\[[state]] - | A string containing the stream's current state, used internally; one of
- "readable" , "closed" , or "errored"
- | \[[reader]] + | A {{ReadableStreamDefaultReader}} or {{ReadableStreamBYOBReader}} + instance, if the stream is [=locked to a reader=], or undefined if it is not |
\[[storedError]] - | A value indicating how the stream failed, to be given as a failure reason or exception - when trying to operate on an errored stream - | \[[state]] + | A string containing the stream's current state, used internally; one
+ of "readable ", "closed ", or "errored "
+ |
\[[storedError]] + | A value indicating how the stream failed, to be given as a failure + reason or exception when trying to operate on an errored stream |
underlyingSource
argument represents the underlying source, as described in
- [[#underlying-source-api]].
-
- The strategy
argument represents the stream's queuing strategy, as described in [[#qs-api]]. If it
- is not provided, the default behavior will be the same as a {{CountQueuingStrategy}} with a high water mark
- of 1.
-A function that is called immediately during creation of the {{ReadableStream}}.
+A function that is called immediately during creation of the {{ReadableStream}}. + +
Typically this is used adapt a [=push source=] by setting up relevant event listeners, as in + the example of [[#example-rs-push-no-backpressure]], or to acquire access to a [=pull source=], + as in [[#example-rs-pull]]. + +
If this setup process is asynchronous, it can return a promise to signal success or failure; + a rejected promise will error the stream. Any thrown exceptions will be re-thrown by the + {{ReadableStream()}} constructor. + +
A function that is called whenever the stream's [=internal queue=] of chunks becomes not full, + i.e. whenever the queue's [=desired size to fill a stream's internal queue|desired size=] becomes + positive. Generally, it will be called repeatedly until the queue reaches its [=high water mark=] + (i.e. until the desired size becomes + non-positive). + +
For [=push sources=], this can be used to resume a paused flow, as in + [[#example-rs-push-backpressure]]. For [=pull sources=], it is used to acquire new [=chunks=] to + enqueue into the stream, as in [[#example-rs-pull]]. + +
This function will not be called until {{UnderlyingSource/start|start()}} successfully + completes. Additionally, it will only be called repeatedly if it enqueues at least one chunk or + fulfills a BYOB request; a no-op {{UnderlyingSource/pull|pull()}} implementation will not be + continually called. + +
If the function returns a promise, then it will not be called again until that promise + fulfills. (If the promise rejects, the stream will become errored.) This is mainly used in the + case of pull sources, where the promise returned represents the process of acquiring a new chunk. + Throwing an exception is treated the same as returning a rejected promise. + +
A function that is called whenever the [=consumer=] [=cancel a readable stream|cancels=] the + stream, via {{ReadableStream/cancel()|stream.cancel()}}, + {{ReadableStreamDefaultReader/cancel()|defaultReader.cancel()}}, or + {{ReadableStreamBYOBReader/cancel()|byobReader.cancel()}}. It takes as its argument the same + value as was passed to those methods by the consumer. + +
Readable streams can additionally be canceled under certain conditions during [=piping=]; see + the definition of the {{ReadableStream/pipeTo()}} method for more details. + +
For all streams, this is generally used to release access to the underlying resource; see for + example [[#example-rs-push-no-backpressure]]. + +
If the shutdown process is asynchronous, it can return a promise to signal success or failure;
+ the result will be communicated via the return value of the cancel()
+ method that was called. Additionally, a rejected promise will error the stream, instead of
+ letting it close. Throwing an exception is treated the same as returning a rejected promise.
+
+
type
(byte streams
+ only)Can be set to "bytes" to signal that the + constructed {{ReadableStream}} is a readable byte stream. This ensures that the resulting + {{ReadableStream}} will successfully be able to vend [=BYOB readers=] via its + {{ReadableStream/getReader()}} method. It also affects the |controller| argument passed to the + {{UnderlyingSource/start|start()}} and {{UnderlyingSource/pull|pull()}} methods; see below. + +
For an example of how to set up a readable byte stream, including using the different + controller interface, see [[#example-rbs-push]]. + +
Setting any value other than "{{ReadableStreamType/bytes}}" or undefined will cause the + {{ReadableStream()}} constructor to throw an exception. + +
autoAllocateChunkSize
(byte streams only)Can be set to a positive integer to cause the implementation to automatically allocate buffers + for the underlying source code to write into. In this case, when a [=consumer=] is using a + [=default reader=], the stream implementation will automatically allocate an {{ArrayBuffer}} of + the given size, so that {{ReadableByteStreamController/byobRequest|controller.byobRequest}} is + always present, as if the consumer was using a [=BYOB reader=]. + +
This is generally used to cut down on the amount of code needed to handle consumers that use + default readers, as can be seen by comparing [[#example-rbs-push]] without auto-allocation to + [[#example-rbs-pull]] with auto-allocation. +
Typically this is used adapt a push source by setting up relevant event listeners, as in the example of - [[#example-rs-push-no-backpressure]], or to acquire access to a pull source, as in [[#example-rs-pull]].
+The type of the |controller| argument passed to the {{UnderlyingSource/start|start()}} and +{{UnderlyingSource/pull|pull()}} methods depends on the value of the {{UnderlyingSource/type}} +option. If {{UnderlyingSource/type}} is set to undefined (including via omission), then +|controller| will be a {{ReadableStreamDefaultController}}. If it's set to +"{{ReadableStreamType/bytes}}", then |controller| will be a {{ReadableByteStreamController}}. -If this setup process is asynchronous, it can return a promise to signal success or failure; a rejected promise - will error the stream. Any thrown exceptions will be re-thrown by the {{ReadableStream()}} constructor.
- +A function that is called whenever the stream's internal queue of chunks becomes not full, i.e. whenever - the queue's desired size becomes positive. Generally, it - will be called repeatedly until the queue reaches its high water mark (i.e. until the desired size becomes non-positive).
- -For push sources, this can be used to resume a paused flow, as in [[#example-rs-push-backpressure]]. For - pull sources, it is used to acquire new chunks to enqueue into the stream, as in - [[#example-rs-pull]].
- -This function will not be called until {{underlying source/start()}} successfully completes. Additionally, it - will only be called repeatedly if it enqueues at least one chunk or fulfills a BYOB request; a no-op - {{underlying source/pull()}} implementation will not be continually called.
- -If the function returns a promise, then it will not be called again until that promise fulfills. (If the - promise rejects, the stream will become errored.) This is mainly used in the case of pull sources, where the - promise returned represents the process of acquiring a new chunk. Throwing an exception is treated the same as - returning a rejected promise.
-stream = new {{ReadableStream/constructor(underlyingSource, strategy)|ReadableStream}}(underlyingSource[, strategy])
A function that is called whenever the consumer cancels the stream, - via {{ReadableStream/cancel()|stream.cancel()}}, {{ReadableStreamDefaultReader/cancel()|defaultReader.cancel()}}, or - {{ReadableStreamBYOBReader/cancel()|byobReader.cancel()}}. It takes as its argument the same value as was passed to - those methods by the consumer.
- -Readable streams can additionally be canceled under certain conditions during piping; see the definition - of the {{ReadableStream/pipeTo()}} method for more details.
+Creates a new {{ReadableStream}} wrapping the provided [=underlying source=]. See + [[#underlying-source-api]] for more details on the underlyingSource argument. + +
The |strategy| argument represents the stream's [=queuing strategy=], as described in + [[#qs-api]]. If it is not provided, the default behavior will be the same as a + {{CountQueuingStrategy}} with a [=high water mark=] of 1. + +
isLocked = stream.{{ReadableStream/locked}}
+ Returns whether or not the readable stream is [=locked to a reader=]. + +
await stream.{{ReadableStream/cancel(reason)|cancel}}([ reason ])
+ [=cancel a readable stream|Cancels=] the stream, signaling a loss of interest in the stream by + a consumer. The supplied reason argument will be given to the underlying source's + {{UnderlyingSource/cancel|cancel()}} method, which might or might not use it. + +
The returned promise will fulfill if the stream shuts down successfully, or reject if the + underlying source signaled that there was an error doing so. Additionally, it will reject with a + {{TypeError}} (without attempting to cancel the stream) if the stream is currently [=locked to a + reader|locked=]. + +
reader = stream.{{ReadableStream/getReader(options)|getReader}}([{ {{ReadableStreamGetReaderOptions/mode}}: "{{ReadableStreamReaderMode/byob}}" }])
+ Creates a reader of the type specified by the {{ReadableStreamGetReaderOptions/mode}} option + and [=locked to a reader|locks=] the stream to the new reader. While the stream is locked, no + other reader can be acquired until this one is [=release a read lock|released=]. + +
This functionality is especially useful for creating abstractions that desire the ability to + consume a stream in its entirety. By getting a reader for the stream, you can ensure nobody else + can interleave reads with yours or cancel the stream, which would interfere with your + abstraction. + +
When {{ReadableStreamGetReaderOptions/mode}} is undefined or not provided, the method creates + a [=default reader=] (an instance of {{ReadableStreamDefaultReader}}). The reader provides the + ability to directly read individual [=chunks=] from the stream via the reader's + {{ReadableStreamDefaultReader/read()}} method. + +
When {{ReadableStreamGetReaderOptions/mode}} is "{{ReadableStreamReaderMode/byob}}", the + method creates a [=BYOB reader=] (an instance of {{ReadableStreamBYOBReader}}). This feature only + works on [=readable byte streams=], i.e. streams which were constructed specifically with the + ability to handle "bring your own buffer" reading. The reader provides the ability to directly + read individual [=chunks=] from the stream via the reader's {{ReadableStreamBYOBReader/read()}} + method, into developer-supplied buffers, allowing more precise control over allocation. -
For all streams, this is generally used to release access to the underlying resource; see for example - [[#example-rs-push-no-backpressure]].
+readable = stream.{{ReadableStream/pipeThrough(transform, options)|pipeThrough}}({ {{ReadableWritablePair/writable}}, {{ReadableWritablePair/readable}} }[, { {{StreamPipeOptions/preventClose}}, {{StreamPipeOptions/preventCancel}}, {{StreamPipeOptions/preventCancel}}, {{StreamPipeOptions/signal}} }])
Provides a convenient, chainable way of [=piping=] this [=readable stream=] through a
+ [=transform stream=] (or any other { writable, readable }
pair). It simply pipes the
+ stream into the writable side of the supplied pair, and returns the readable side for further use.
-
If the shutdown process is asynchronous, it can return a promise to signal success or failure; the result will be
- communicated via the return value of the cancel()
method that was called. Additionally, a rejected
- promise will error the stream, instead of letting it close. Throwing an exception is treated the same as returning
- a rejected promise.
Piping a stream will [=locked to a reader|lock=] it for the duration of the pipe, preventing + any other consumer from acquiring a reader. -
type
(byte streams only)Can be set to "bytes"
to signal that the constructed {{ReadableStream}} is a readable byte
- stream. This ensures that the resulting {{ReadableStream}} will successfully be able to vend BYOB readers
- via its {{ReadableStream/getReader()}} method. It also affects the controller
argument passed to the
- {{underlying source/start()}} and {{underlying source/pull()}} methods; see below.
await stream.{{ReadableStream/pipeTo(destination, options)|pipeTo}}(destination[, { {{StreamPipeOptions/preventClose}}, {{StreamPipeOptions/preventCancel}}, {{StreamPipeOptions/preventCancel}}, {{StreamPipeOptions/signal}} }])
[=piping|Pipes=] this [=readable stream=] to a given [=writable stream=] |destination|. The + way in which the piping process behaves under various error conditions can be customized with a + number of passed options. It returns a promise that fulfills when the piping process completes + successfully, or rejects if any errors were encountered. -
For an example of how to set up a readable byte stream, including using the different controller interface, see - [[#example-rbs-push]].
+ Piping a stream will [=locked to a reader|lock=] it for the duration of the pipe, preventing any + other consumer from acquiring a reader. -Setting any value other than "bytes"
or
autoAllocateChunkSize
(byte streams only)Can be set to a positive integer to cause the implementation to automatically allocate buffers for the - underlying source code to write into. In this case, when a consumer is using a default reader, the - stream implementation will automatically allocate an {{ArrayBuffer}} of the given size, so that - {{ReadableByteStreamController/byobRequest|controller.byobRequest}} is always present, as if the consumer was using - a BYOB reader.
- -This is generally used to cut down on the amount of code needed to handle consumers that use default readers, as - can be seen by comparing [[#example-rbs-push]] without auto-allocation to [[#example-rbs-pull]] with - auto-allocation.
-The {{StreamPipeOptions/signal}} option can be set to an {{AbortSignal}} to allow aborting an + ongoing pipe operation via the corresponding {{AbortController}}. In this case, this source + [=readable stream=] will be [=cancel a readable stream|canceled=], and |destination| [=abort a + writable stream|aborted=], unless the respective options {{StreamPipeOptions/preventCancel}} or + {{StreamPipeOptions/preventAbort}} are set. + +
[branch1, branch2] = stream.{{ReadableStream/tee()|tee}}()
+ [=tee a readable stream|Tees=] this readable stream, returning a two-element array containing + the two resulting branches as new {{ReadableStream}} instances. + +
Teeing a stream will [=locked to a reader|lock=] it, preventing any other consumer from + acquiring a reader. To [=cancel a readable stream|cancel=] the stream, cancel both of the + resulting branches; a composite cancellation reason will then be propagated to the stream's + [=underlying source=]. + +
Note that the [=chunks=] seen in each branch will be the same object. If the chunks are not + immutable, this could allow interference between the two branches.
controller
argument passed to the {{underlying source/start()}} and
-{{underlying source/pull()}} methods depends on the value of the type
-option. If type
is set to undefined
(including via omission),
-controller
will be a {{ReadableStreamDefaultController}}. If it's set to "bytes"
,
-controller
will be a {{ReadableByteStreamController}}.
-
+We cannot declare the |underlyingSource| argument as having the + {{UnderlyingSource}} type directly, because doing so would lose the reference to the original + object. We need to retain the object so we can [=invoke=] the various methods on it. + 1. Perform ! [$InitializeReadableStream$]([=this=]). + 1. If |underlyingSourceDict|["{{UnderlyingSource/type}}"] is "{{ReadableStreamType/bytes}}": + 1. If |strategy|["{{QueuingStrategy/size}}"] [=map/exists=], throw a {{RangeError}} exception. + 1. Let |highWaterMark| be ? [$ExtractHighWaterMark$](|strategy|, 0). + 1. Perform ? [$SetUpReadableByteStreamControllerFromUnderlyingSource$]([=this=], + |underlyingSource|, |underlyingSourceDict|, |highWaterMark|). + 1. Otherwise, + 1. Assert: |underlyingSourceDict|["{{UnderlyingSource/type}}"] does not [=map/exist=]. + 1. Let |sizeAlgorithm| be ! [$ExtractSizeAlgorithm$](|strategy|). + 1. Let |highWaterMark| be ? [$ExtractHighWaterMark$](|strategy|, 1). + 1. Perform ? [$SetUpReadableStreamDefaultControllerFromUnderlyingSource$]([=this=], + |underlyingSource|, |underlyingSourceDict|, |highWaterMark|, |sizeAlgorithm|).
locked
getter returns whether or not the readable stream is locked to a reader.
-cancel
method cancels the stream, signaling a loss of interest
- in the stream by a consumer. The supplied reason
argument will be given to the underlying source's
- {{underlying source/cancel()}} method, which might or might not use it.
+ 1. Return ! [$IsReadableStreamLocked$]([=this=]).
getIterator
method returns an async iterator which can be used to consume the stream. The
- {{ReadableStreamAsyncIteratorPrototype/return()}} method of this iterator object will, by default,
- cancel the stream; it will also release the reader.
+ 1. If ! [$IsReadableStreamLocked$]([=this=]) is true, return [=a promise rejected with=] a
+ {{TypeError}} exception.
+ 1. Return ! [$ReadableStreamCancel$]([=this=], |reason|).
getReader
method creates a reader of the type specified by the mode
option and locks the stream to the new reader. While the stream is locked, no other reader can be
- acquired until this one is released.
-
- This functionality is especially useful for creating abstractions that desire the ability to consume a stream in its
- entirety. By getting a reader for the stream, you can ensure nobody else can interleave reads with yours or cancel
- the stream, which would interfere with your abstraction.
-
- When mode
is mode
is "byob"
, the getReader
method creates a BYOB reader (an
- instance of {{ReadableStreamBYOBReader}}). This feature only works on readable byte streams, i.e. streams which
- were constructed specifically with the ability to handle "bring your own buffer" reading. The reader provides the
- ability to directly read individual chunks from the stream via the reader's {{ReadableStreamBYOBReader/read()}}
- method, into developer-supplied buffers, allowing more precise control over allocation.
-
- function readAllChunks(readableStream) {
- const reader = readableStream.getReader();
- const chunks = [];
+
+ function readAllChunks(readableStream) {
+ const reader = readableStream.getReader();
+ const chunks = [];
- return pump();
+ return pump();
- function pump() {
- return reader.read().then(({ value, done }) => {
- if (done) {
- return chunks;
- }
+ function pump() {
+ return reader.read().then(({ value, done }) => {
+ if (done) {
+ return chunks;
+ }
- chunks.push(value);
- return pump();
- });
- }
+ chunks.push(value);
+ return pump();
+ });
}
-
-
- Note how the first thing it does is obtain a reader, and from then on it uses the reader exclusively. This ensures
- that no other consumer can interfere with the stream, either by reading chunks or by
- canceling the stream.
-pipeThrough
method provides a convenient, chainable way of piping this readable stream
- through a transform stream (or any other { writable, readable }
pair). It simply pipes the stream
- into the writable side of the supplied pair, and returns the readable side for further use.
-
- Piping a stream will lock it for the duration of the pipe, preventing any other
- consumer from acquiring a reader.
-
- httpResponseBody
- .pipeThrough(decompressorTransform)
- .pipeThrough(ignoreNonImageFilesTransform)
- .pipeTo(mediaGallery);
-
-pipeTo
method pipes this readable stream to a given writable
- stream. The way in which the piping process behaves under various error conditions can be customized with a
- number of passed options. It returns a promise that fulfills when the piping process completes successfully, or
- rejects if any errors were encountered.
-
- Piping a stream will lock it for the duration of the pipe, preventing any other
- consumer from acquiring a reader.
-
- Errors and closures of the source and destination streams propagate as follows:
-
- An error in the source readable stream will abort the destination
- writable stream, unless preventAbort
is truthy. The returned promise will be rejected with the
- source's error, or with any error that occurs during aborting the destination.
An error in the destination writable stream will cancel the
- source readable stream, unless preventCancel
is truthy. The returned promise will be rejected
- with the destination's error, or with any error that occurs during canceling the source.
When the source readable stream closes, the destination writable stream will be closed, unless
- preventClose
is true. The returned promise will be fulfilled once this process completes, unless an
- error is encountered while closing the destination, in which case it will be rejected with that error.
If the destination writable stream starts out closed or closing, the source readable stream
- will be canceled, unless preventCancel
is true. The returned
- promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs
- during canceling the source.
signal
option can be set to an {{AbortSignal}} to allow aborting an ongoing pipe operation via the
- corresponding {{AbortController}}. In this case, the source readable stream will be canceled, and the destination writable stream aborted, unless
- the respective options preventCancel
or preventAbort
are set.
+ }
+
+ Note how the first thing it does is obtain a reader, and from then on it uses the reader
+ exclusively. This ensures that no other consumer can interfere with the stream, either by reading
+ chunks or by [=cancel a readable stream|canceling=] the stream.
+ tee
method tees this readable stream, returning a two-element
- array containing the two resulting branches as new {{ReadableStream}} instances.
-
- Teeing a stream will lock it, preventing any other consumer from acquiring a reader.
- To cancel the stream, cancel both of the resulting branches; a composite
- cancellation reason will then be propagated to the stream's underlying source.
-
- Note that the chunks seen in each branch will be the same object. If the chunks are not immutable, this could
- allow interference between the two branches.
+cacheEntry
representing an
- on-disk file, and another writable stream httpRequestBody
representing an upload to a remote server,
- you could pipe the same readable stream to both destinations at once:
-
-
- const [forLocal, forRemote] = readableStream.tee();
-
- Promise.all([
- forLocal.pipeTo(cacheEntry),
- forRemote.pipeTo(httpRequestBody)
- ])
- .then(() => console.log("Saved the stream to the cache and also uploaded it!"))
- .catch(e => console.error("Either caching or uploading failed: ", e));
-
+
- The @@asyncIterator
method is an alias of {{ReadableStream/getIterator()}}.
-
@@asyncIterator
method is the same function object as the initial value of the
-{{ReadableStream/getIterator()}} method.
-
-Internal Slot | -Description (non-normative) | -
---|---|
\[[asyncIteratorReader]] - | A {{ReadableStreamDefaultReader}} instance - |
\[[preventCancel]] - | A boolean value indicating if the stream will be canceled when the async iterator's {{ReadableStreamAsyncIteratorPrototype/return()}} method is called - |
Other specifications ought to leave forAuthorCode as its default value of
-{ value, done }
object
-to authors. See the note regarding ReadableStreamCreateReadResult for more
-information.
CreateReadableStream throws an exception if and only if the supplied startAlgorithm -throws.
- -CreateReadableByteStream throws an exception if and only if the supplied startAlgorithm -throws.
- -In this standard ReadableStreamTee is always called with cloneForBranch2 set to
-
It's frequently inefficient to read chunks that are too small or too large. Other information - might be factored in to determine the optimal chunk size.
- * Reads or writes should not be delayed for reasons other than these backpressure signals. -An implementation that waits for each write to successfully - complete before proceeding to the next read/write operation violates this recommendation. In doing so, such an - implementation makes the internal queue of _dest_ useless, as it ensures _dest_ always contains at most - one queued chunk.
- * Shutdown must stop activity: if _shuttingDown_ becomes *true*, the user agent must not - initiate further reads from _reader_, and must only perform writes of already-read chunks, as described - below. In particular, the user agent must check the below conditions before performing any reads or writes, - since they might lead to immediate shutdown. - * Error and close states must be propagated: the following conditions must be applied in order. - 1. Errors must be propagated forward: if _source_.[[state]] is or becomes `"errored"`, then - 1. If _preventAbort_ is *false*, shutdown with an action of ! - WritableStreamAbort(_dest_, _source_.[[storedError]]) and with _source_.[[storedError]]. - 1. Otherwise, shutdown with _source_.[[storedError]]. - 1. Errors must be propagated backward: if _dest_.[[state]] is or becomes `"errored"`, then - 1. If _preventCancel_ is *false*, shutdown with an action of ! - ReadableStreamCancel(_source_, _dest_.[[storedError]]) and with _dest_.[[storedError]]. - 1. Otherwise, shutdown with _dest_.[[storedError]]. - 1. Closing must be propagated forward: if _source_.[[state]] is or becomes `"closed"`, then - 1. If _preventClose_ is *false*, shutdown with an action of ! - WritableStreamDefaultWriterCloseWithErrorPropagation(_writer_). - 1. Otherwise, shutdown. - 1. Closing must be propagated backward: if ! WritableStreamCloseQueuedOrInFlight(_dest_) is *true* - or _dest_.[[state]] is `"closed"`, then - 1. Assert: no chunks have been read or written. - 1. Let _destClosed_ be a new *TypeError*. - 1. If _preventCancel_ is *false*, shutdown with an action of ! - ReadableStreamCancel(_source_, _destClosed_) and with _destClosed_. - 1. Otherwise, shutdown with _destClosed_. - * Shutdown with an action: if any of the above requirements ask to - shutdown with an action _action_, optionally with an error _originalError_, then: - 1. If _shuttingDown_ is *true*, abort these substeps. - 1. Set _shuttingDown_ to *true*. - 1. If _dest_.[[state]] is `"writable"` and ! WritableStreamCloseQueuedOrInFlight(_dest_) is *false*, - 1. If any chunks have been read but not yet written, write them to _dest_. - 1. Wait until every chunk that has been read has been written (i.e. the corresponding promises have - settled). - 1. Let _p_ be the result of performing _action_. - 1. Upon fulfillment of _p_, finalize, passing along _originalError_ if - it was given. - 1. Upon rejection of _p_ with reason _newError_, finalize with - _newError_. - * Shutdown: if any of the above requirements or steps ask to shutdown, optionally - with an error _error_, then: - 1. If _shuttingDown_ is *true*, abort these substeps. - 1. Set _shuttingDown_ to *true*. - 1. If _dest_.[[state]] is `"writable"` and ! WritableStreamCloseQueuedOrInFlight(_dest_) is *false*, - 1. If any chunks have been read but not yet written, write them to _dest_. - 1. Wait until every chunk that has been read has been written (i.e. the corresponding promises have - settled). - 1. Finalize, passing along _error_ if it was given. - * Finalize: both forms of shutdown will eventually ask to finalize, optionally with - an error _error_, which means to perform the following steps: - 1. Perform ! WritableStreamDefaultWriterRelease(_writer_). - 1. Perform ! ReadableStreamReaderGenericRelease(_reader_). - 1. If _signal_ is not *undefined*, remove _abortAlgorithm_ from _signal_. - 1. If _error_ was given, reject _promise_ with _error_. - 1. Otherwise, resolve _promise_ with *undefined*. - 1. Return _promise_. -- Various abstract operations performed here include object creation (often of promises), which usually would require - specifying a realm for the created object. However, because of the locking, none of these objects can be observed by - author code. As such, the realm used to create them does not matter. -
- -"closed"
, but stream.\[[closeRequested]] is
- close
method to be called and silently do nothing, since the cancelation was outside the
- control of the underlying source.
+cacheEntry
representing an on-disk file, and another writable stream
+ httpRequestBody
representing an upload to a remote server, you could pipe the same
+ readable stream to both destinations at once:
+
+ Object.prototype.then
. For internal use, particularly in {{ReadableStream/pipeTo()}} and in other
- specifications, it is important that reads not be observable by author code—even if that author code has tampered with
- Object.prototype
. For this reason, a { value, done }
objects,
- even in specifications. Although it is conceivable we could rephrase all of the internal algorithms to not use
- promises and not use JavaScript objects, and instead only package up the results into promise-for-{ value, done
- }
when a read()
method is called, this would be a large undertaking, which we have not done. See
- whatwg/infra#181 for more background on this subject.
-ReadableStreamDefaultReader
- class ReadableStreamDefaultReader {
- constructor(stream)
+The Web IDL definition for the {{ReadableStreamDefaultReader}} class is given as follows:
- get closed()
+
+[Exposed=(Window,Worker,Worklet)]
+interface ReadableStreamDefaultReader {
+ constructor(ReadableStream stream);
- cancel(reason)
- read()
- releaseLock()
- }
-
+ readonly attribute PromiseInternal Slot | -Description (non-normative) | -
---|---|
\[[closedPromise]] - | A promise returned by the reader's {{ReadableStreamDefaultReader/closed}} getter - | Internal Slot + | Description (non-normative) + |
\[[forAuthorCode]] - | A boolean flag indicating whether this reader is visible to author code - | \[[closedPromise]] + | A promise returned by the reader's + {{ReadableStreamDefaultReader/closed}} getter |
\[[ownerReadableStream]] - | A {{ReadableStream}} instance that owns this reader - | \[[forAuthorCode]] + | A boolean flag indicating whether this reader is visible to author code |
\[[readRequests]] - | A List of promises returned by calls to the reader's - {{ReadableStreamDefaultReader/read()}} method that have not yet been resolved, due to the consumer - requesting chunks sooner than they are available; also used for the IsReadableStreamDefaultReader brand check - | \[[ownerReadableStream]] + | A {{ReadableStream}} instance that owns this reader + |
\[[readRequests]] + | A [=list=] of promises returned by calls to the reader's + {{ReadableStreamDefaultReader/read()}} method that have not yet been resolved, due to the + [=consumer=] requesting [=chunks=] sooner than they are available |
ReadableStreamDefaultReader
constructor is generally not meant to be used directly; instead, a
- stream's {{ReadableStream/getReader()}} method ought to be used.
-reader = new {{ReadableStreamDefaultReader(stream)|ReadableStreamDefaultReader}}(|stream|)
+ This is equivalent to calling |stream|.{{ReadableStream/getReader()}}
.
-
await reader.{{ReadableStreamDefaultReader/closed}}
+ Returns a promise that will be fulfilled when the stream becomes closed, or rejected if the + stream ever errors or the reader's lock is [=release a read lock|released=] before the stream + finishes closing. -
await reader.{{ReadableStreamDefaultReader/cancel(reason)|cancel}}([ reason ])
+ If the reader is [=active reader|active=], behaves the same as
+ |stream|.{{ReadableStream/cancel(reason)|cancel}}(reason)
.
-
{ value, done } = await reader.{{ReadableStreamDefaultReader/read()|read}}()
+ Returns a promise that allows access to the next [=chunk=] from the stream's internal queue, if + available. -
closed
getter returns a promise that will be fulfilled when the stream becomes closed, or rejected if
- the stream ever errors or the reader's lock is released before the stream finishes
- closing.
-{ value: theChunk, done: false }
.
-{ value: undefined, done: true }
.
-cancel
method behaves the same as that for the
- associated stream.
-If reading a chunk causes the queue to become empty, more data will be pulled from the
+ [=underlying source=].
-
reader.{{ReadableStreamDefaultReader/releaseLock()|releaseLock}}()
+ [=release a read lock|Releases the reader's lock=] on the corresponding stream. After the lock + is released, the reader is no longer [=active reader|active=]. If the associated stream is errored + when the lock is released, the reader will appear errored in the same way from now on; otherwise, + the reader will appear closed. -
A reader's lock cannot be released while it still has a pending read request, i.e., if a + promise returned by the reader's {{ReadableStreamDefaultReader/read()}} method has not yet been + settled. Attempting to do so will throw a {{TypeError}} and leave the reader locked to the stream. +
read
method will return a promise that allows access to the next chunk from the stream's
- internal queue, if available.
-
- { value: theChunk, done: false }
.
- { value: undefined, done: true }
.
- releaseLock
method releases the reader's lock on the corresponding
- stream. After the lock is released, the reader is no longer active. If the associated
- stream is errored when the lock is released, the reader will appear errored in the same way from now on; otherwise,
- the reader will appear closed.
+ReadableStreamBYOBReader
- class ReadableStreamBYOBReader {
- constructor(stream)
+The Web IDL definition for the {{ReadableStreamBYOBReader}} class is given as follows:
- get closed()
+
+[Exposed=(Window,Worker,Worklet)]
+interface ReadableStreamBYOBReader {
+ constructor(ReadableStream stream);
- cancel(reason)
- read(view)
- releaseLock()
- }
-
+ readonly attribute PromiseInternal Slot | -Description (non-normative) | -
---|---|
Internal Slot + | Description (non-normative) + |
\[[closedPromise]] - | A promise returned by the reader's {{ReadableStreamBYOBReader/closed}} getter - | \[[closedPromise]] + | A promise returned by the reader's + {{ReadableStreamBYOBReader/closed}} getter |
\[[forAuthorCode]] - | A boolean flag indicating whether this reader is visible to author code - | \[[forAuthorCode]] + | A boolean flag indicating whether this reader is visible to author code |
\[[ownerReadableStream]] - | A {{ReadableStream}} instance that owns this reader - | \[[ownerReadableStream]] + | A {{ReadableStream}} instance that owns this reader |
\[[readIntoRequests]] - | A List of promises returned by calls to the reader's - {{ReadableStreamBYOBReader/read(view)}} method that have not yet been resolved, due to the consumer - requesting chunks sooner than they are available; also used for the IsReadableStreamBYOBReader brand check - | \[[readIntoRequests]] + | A [=list=] of promises returned by calls to the reader's + {{ReadableStreamBYOBReader/read(view)}} method that have not yet been resolved, due to the + [=consumer=] requesting [=chunks=] sooner than they are available |
ReadableStreamBYOBReader
constructor is generally not meant to be used directly; instead, a stream's
- {{ReadableStream/getReader()}} method ought to be used.
-reader = new {{ReadableStreamBYOBReader(stream)|ReadableStreamBYOBReader}}(|stream|)
+ This is equivalent to calling |stream|.{{ReadableStream/getReader}}({
+ {{ReadableStreamGetReaderOptions/mode}}: "{{ReadableStreamReaderMode/byob}}" })
.
+
+
await reader.{{ReadableStreamBYOBReader/closed}}
+ Returns a promise that will be fulfilled when the stream becomes closed, or rejected if the + stream ever errors or the reader's lock is [=release a read lock|released=] before the stream + finishes closing. + +
await reader.{{ReadableStreamBYOBReader/cancel(reason)|cancel}}([ reason ])
+ If the reader is [=active reader|active=], behaves the same
+ |stream|.{{ReadableStream/cancel(reason)|cancel}}(reason)
.
+
+
{ value, done } = await reader.{{ReadableStreamBYOBReader/read()|read}}(view)
+ Attempts to reads bytes into |view|, and returns a promise resolved with the result: + +
{ value: theChunk, done: false }
. In this case, |view| will be
+ [=ArrayBuffer/detached=] and no longer usable, but theChunk
will be a new view (of
+ the same type) onto the same backing memory region, with the chunk's data written into it.
+
+ { value: undefined, done: true }
.
+
+ If reading a chunk causes the queue to become empty, more data will be pulled from the + [=underlying source=]. -
closed
getter returns a promise that will be fulfilled when the stream becomes closed, or rejected if
- the stream ever errors or the reader's lock is released before the stream finishes
- closing.
-reader.{{ReadableStreamBYOBReader/releaseLock()|releaseLock}}()
+ [=release a read lock|Releases the reader's lock=] on the corresponding stream. After the lock
+ is released, the reader is no longer [=active reader|active=]. If the associated stream is errored
+ when the lock is released, the reader will appear errored in the same way from now on; otherwise,
+ the reader will appear closed.
-
A reader's lock cannot be released while it still has a pending read request, i.e., if a + promise returned by the reader's {{ReadableStreamBYOBReader/read()}} method has not yet been + settled. Attempting to do so will throw a {{TypeError}} and leave the reader locked to the stream. +
cancel
method behaves the same as that for the
- associated stream.
+ 1. Perform ? [$SetUpReadableStreamBYOBReader$]([=this=], |stream|).
read
method will write read bytes into view
and return a promise resolved with a
- possibly transferred buffer as described below.
-
- { value: theChunk, done: false }
.
- { value: undefined, done: true }
.
- releaseLock
method releases the reader's lock on the corresponding
- stream. After the lock is released, the reader is no longer active. If the associated
- stream is errored when the lock is released, the reader will appear errored in the same way from now on; otherwise,
- the reader will appear closed.
+ 1. If [=this=].\[[ownerReadableStream]] is undefined, return [=a promise rejected with=] a
+ {{TypeError}} exception.
+ 1. If |view|.\[[ByteLength]] is 0, return [=a promise rejected with=] a {{TypeError}} exception.
+ 1. Return ! [$ReadableStreamBYOBReaderRead$]([=this=], |view|).
+ReadableStreamDefaultController
- class ReadableStreamDefaultController {
- constructor() // always throws
+Interface definition
- get desiredSize()
+The Web IDL definition for the {{ReadableStreamDefaultController}} class is given as follows:
- close()
- enqueue(chunk)
- error(e)
- }
-
+Internal Slot | -Description (non-normative) | -
---|---|
\[[cancelAlgorithm]] - | A promise-returning algorithm, taking one argument (the cancel reason), which communicates - a requested cancelation to the underlying source - | Internal Slot | +Description (non-normative) | +
\[[closeRequested]] - | A boolean flag indicating whether the stream has been closed by its underlying - source, but still has chunks in its internal queue that have not yet been read - | \[[cancelAlgorithm]] + | A promise-returning algorithm, taking one argument (the cancel reason), + which communicates a requested cancelation to the [=underlying source=] |
\[[controlledReadableStream]] - | The {{ReadableStream}} instance controlled - | \[[closeRequested]] + | A boolean flag indicating whether the stream has been closed by its + [=underlying source=], but still has [=chunks=] in its internal queue that have not yet been + read |
\[[pullAgain]] - | A boolean flag set to | \[[controlledReadableStream]] + | The {{ReadableStream}} instance controlled |
\[[pullAlgorithm]] - | A promise-returning algorithm that pulls data from the underlying source - | \[[pullAgain]] + | A boolean flag set to true if the stream's mechanisms requested a call + to the [=underlying source=]'s pull algorithm to pull more data, but the pull could not yet be + done since a previous call is still executing |
\[[pulling]] - | A boolean flag set to | \[[pullAlgorithm]] + | A promise-returning algorithm that pulls data from the [=underlying + source=] |
\[[queue]] - | A List representing the stream's internal queue of chunks - | \[[pulling]] + | A boolean flag set to true while the [=underlying source=]'s pull + algorithm is executing and the returned promise has not yet fulfilled, used to prevent reentrant + calls |
\[[queueTotalSize]] - | The total size of all the chunks stored in \[[queue]] (see [[#queue-with-sizes]]) - | \[[queue]] + | A [=list=] representing the stream's internal queue of [=chunks=] |
\[[started]] - | A boolean flag indicating whether the underlying source has finished starting - | \[[queueTotalSize]] + | The total size of all the chunks stored in \[[queue]] (see + [[#queue-with-sizes]]) |
\[[strategyHWM]] - | A number supplied to the constructor as part of the stream's queuing strategy, - indicating the point at which the stream will apply backpressure to its underlying source - | \[[started]] + | A boolean flag indicating whether the [=underlying source=] has + finished starting |
\[[strategySizeAlgorithm]] - | An algorithm to calculate the size of enqueued chunks, as part of the stream's - queuing strategy - | \[[strategyHWM]] + | A number supplied to the constructor as part of the stream's [=queuing + strategy=], indicating the point at which the stream will apply [=backpressure=] to its + [=underlying source=] + |
\[[strategySizeAlgorithm]] + | An algorithm to calculate the size of enqueued [=chunks=], as part of + the stream's [=queuing strategy=] |
ReadableStreamDefaultController
constructor cannot be used directly;
- {{ReadableStreamDefaultController}} instances are created automatically during {{ReadableStream}} construction.
-desiredSize = controller.{{ReadableStreamDefaultController/desiredSize}}
+ Returns the [=desired size to fill a stream's internal queue|desired size to fill the + controlled stream's internal queue=]. It can be negative, if the queue is over-full. An + [=underlying source=] ought to use this information to determine when and how to apply + [=backpressure=]. + +
controller.{{ReadableStreamDefaultController/close()|close}}()
+ Closes the controlled readable stream. [=Consumers=] will still be able to read any + previously-enqueued [=chunks=] from the stream, but once those are read, the stream will become + closed. + +
controller.{{ReadableStreamDefaultController/enqueue()|enqueue}}(chunk)
+ Enqueues the given [=chunk=] chunk in the controlled readable stream. + +
controller.{{ReadableStreamDefaultController/error()|error}}(e)
+ Errors the controlled readable stream, making all future interactions with it fail with the + given error e. +
desiredSize
getter returns the desired size
- to fill the controlled stream's internal queue. It can be negative, if the queue is over-full. An underlying
- source ought to use this information to determine when and how to apply backpressure.
+ 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$]([=this=]) is false, throw a
+ {{TypeError}} exception.
+ 1. Perform ! [$ReadableStreamDefaultControllerClose$]([=this=]).
close
method will close the controlled readable stream. Consumers will still be able to read
- any previously-enqueued chunks from the stream, but once those are read, the stream will become closed.
+ 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$]([=this=]) is false, throw a
+ {{TypeError}} exception.
+ 1. Perform ? [$ReadableStreamDefaultControllerEnqueue$]([=this=], |chunk|).
enqueue
method will enqueue a given chunk in the controlled readable stream.
+ 1. Perform ! [$ReadableStreamDefaultControllerError$]([=this=], |e|).
error
method will error the readable stream, making all future interactions with it fail with the
- given error e
.
-The results of this algorithm are not currently observable, but could become so if JavaScript eventually -adds weak references. But even without that factor, -implementations will likely want to include similar steps.
- -"readable"
, happens when the stream is errored via {{ReadableStreamDefaultController/error(e)}}, or
- when it is closed without its controller's close
method ever being called: e.g., if the stream was closed
- by a call to {{ReadableStream/cancel(reason)}}.
-ReadableByteStreamController
- class ReadableByteStreamController {
- constructor() // always throws
+Interface definition
- get byobRequest()
- get desiredSize()
+The Web IDL definition for the {{ReadableByteStreamController}} class is given as follows:
- close()
- enqueue(chunk)
- error(e)
- }
-
+Internal Slot | -Description (non-normative) | -
---|---|
Internal Slot | +Description (non-normative) | +
\[[autoAllocateChunkSize]] - | A positive integer, when the automatic buffer allocation feature is enabled. In that case,
- this value specifies the size of buffer to allocate. It is | \[[autoAllocateChunkSize]] + | A positive integer, when the automatic buffer allocation feature is + enabled. In that case, this value specifies the size of buffer to allocate. It is undefined + otherwise. |
\[[byobRequest]] - | A {{ReadableStreamBYOBRequest}} instance representing the current BYOB pull request,
- or | \[[byobRequest]] + | A {{ReadableStreamBYOBRequest}} instance representing the current BYOB + pull request, or undefined if there are no pending requests |
\[[cancelAlgorithm]] - | A promise-returning algorithm, taking one argument (the cancel reason), which communicates - a requested cancelation to the underlying source - | \[[cancelAlgorithm]] + | A promise-returning algorithm, taking one argument (the cancel reason), + which communicates a requested cancelation to the [=underlying byte source=] |
\[[closeRequested]] - | A boolean flag indicating whether the stream has been closed by its underlying byte - source, but still has chunks in its internal queue that have not yet been read - | \[[closeRequested]] + | A boolean flag indicating whether the stream has been closed by its + [=underlying byte source=], but still has [=chunks=] in its internal queue that have not yet been + read |
\[[controlledReadableByteStream]] - | The {{ReadableStream}} instance controlled - | \[[controlledReadableStream]] + | The {{ReadableStream}} instance controlled |
\[[pullAgain]] - | A boolean flag set to | \[[pullAgain]] + | A boolean flag set to true if the stream's mechanisms requested a call + to the [=underlying byte source=]'s pull algorithm to pull more data, but the pull could not yet + be done since a previous call is still executing |
\[[pullAlgorithm]] - | A promise-returning algorithm that pulls data from the underlying source - | \[[pullAlgorithm]] + | A promise-returning algorithm that pulls data from the [=underlying + byte source=] |
\[[pulling]] - | A boolean flag set to | \[[pulling]] + | A boolean flag set to true while the [=underlying byte source=]'s pull + algorithm is executing and the returned promise has not yet fulfilled, used to prevent reentrant + calls |
\[[pendingPullIntos]] - | A List of descriptors representing pending BYOB pull requests - | \[[pendingPullIntos]] + | A [=list=] of descriptors representing pending BYOB pull requests |
\[[queue]] - | A List representing the stream's internal queue of chunks - | \[[queue]] + | A [=list=] representing the stream's internal queue of [=chunks=] |
\[[queueTotalSize]] - | The total size (in bytes) of all the chunks stored in \[[queue]] - | \[[queueTotalSize]] + | The total size, in bytes, of all the chunks stored in \[[queue]] (see + [[#queue-with-sizes]]) |
\[[started]] - | A boolean flag indicating whether the underlying source has finished starting - | \[[started]] + | A boolean flag indicating whether the [=underlying byte source=] has + finished starting |
\[[strategyHWM]] - | A number supplied to the constructor as part of the stream's queuing strategy, - indicating the point at which the stream will apply backpressure to its underlying byte source - | \[[strategyHWM]] + | A number supplied to the constructor as part of the stream's [=queuing + strategy=], indicating the point at which the stream will apply [=backpressure=] to its + [=underlying byte source=] |
Although {{ReadableByteStreamController}} instances have \[[queue]] and \[[queueTotalSize]] slots, we do not use - most of the abstract operations in [[#queue-with-sizes]] on them, as the way in which we manipulate this queue is - rather different than the others in the spec. Instead, we update the two slots together manually.
+Although {{ReadableByteStreamController}} instances have \[[queue]] and \[[queueTotalSize]] + slots, we do not use most of the abstract operations in [[#queue-with-sizes]] on them, as the way + in which we manipulate this queue is rather different than the others in the spec. Instead, we + update the two slots together manually. -
This might be cleaned up in a future spec refactoring.
+This might be cleaned up in a future spec refactoring.
byobRequest = controller.{{ReadableByteStreamController/byobRequest}}
+ Returns the current BYOB pull request. + +
desiredSize = controller.{{ReadableByteStreamController/desiredSize}}
+ Returns the [=desired size to fill a stream's internal queue|desired size to fill the + controlled stream's internal queue=]. It can be negative, if the queue is over-full. An + [=underlying byte source=] ought to use this information to determine when and how to apply + [=backpressure=]. + +
controller.{{ReadableByteStreamController/close()|close}}()
+ Closes the controlled readable stream. [=Consumers=] will still be able to read any + previously-enqueued [=chunks=] from the stream, but once those are read, the stream will become + closed. + +
controller.{{ReadableByteStreamController/enqueue()|enqueue}}(chunk)
+ Enqueues the given [=chunk=] chunk in the controlled readable stream. The + chunk has to be an {{ArrayBufferView}} instance, or else a {{TypeError}} will be thrown. + +
controller.{{ReadableByteStreamController/error()|error}}(e)
+ Errors the controlled readable stream, making all future interactions with it fail with the + given error e. +
ReadableByteStreamController
constructor cannot be used directly;
- {{ReadableByteStreamController}} instances are created automatically during {{ReadableStream}} construction.
+byobRequest
getter returns the current BYOB pull request.
+ 1. Return ! [$ReadableByteStreamControllerGetDesiredSize$]([=this=]).
desiredSize
getter returns the desired size
- to fill the controlled stream's internal queue. It can be negative, if the queue is over-full. An underlying
- source ought to use this information to determine when and how to apply backpressure.
+ 1. If [=this=].\[[closeRequested]] is true, throw a {{TypeError}} exception.
+ 1. If [=this=].\[[controlledReadableStream]].\[[state]] is not "`readable`", throw a {{TypeError}}
+ exception.
+ 1. Perform ? [$ReadableByteStreamControllerClose$]([=this=]).
close
method will close the controlled readable stream. Consumers will still be able to read
- any previously-enqueued chunks from the stream, but once those are read, the stream will become closed.
+ 1. If [=this=].\[[closeRequested]] is true, throw a {{TypeError}} exception.
+ 1. If [=this=].\[[controlledReadableStream]].\[[state]] is not "`readable`", throw a {{TypeError}}
+ exception.
+ 1. Return ! [$ReadableByteStreamControllerEnqueue$]([=this=], |chunk|).
enqueue
method will enqueue a given chunk in the controlled readable stream.
+ 1. Perform ! [$ReadableByteStreamControllerError$]([=this=], |e|).
error
method will error the readable stream, making all future interactions with it fail with the
- given error e
.
-ReadableStreamBYOBRequest
- class ReadableStreamBYOBRequest {
- constructor(controller, view)
+The {{ReadableStreamBYOBRequest}} class represents a pull-into request in a
+{{ReadableByteStreamController}}.
- get view()
+Interface definition
- respond(bytesWritten)
- respondWithNewView(view)
- }
-
+The Web IDL definition for the {{ReadableStreamBYOBRequest}} class is given as follows:
-Internal Slot | -Description (non-normative) | -
---|---|
\[[associatedReadableByteStreamController]] - | The parent {{ReadableByteStreamController}} instance - |
\[[view]] - | A typed array representing the destination region to which the controller can write - generated data - | Internal Slot | +Description (non-normative) | + +
\[[controller]] + | The parent {{ReadableByteStreamController}} instance + |
\[[view]] + | A [=typed array=] representing the destination region to which the + controller can write generated data |
view = byobRequest.{{ReadableStreamBYOBRequest/view}}
+ Returns the view for writing in to. -
byobRequest.{{ReadableStreamBYOBRequest/respond()|respond}}(bytesWritten)
+ Indicates to the associated [=readable byte stream=] that bytesWritten bytes + were written into {{ReadableStreamBYOBRequest/view}}, causing the result be surfaced to the + [=consumer=]. -
After this method is called, {{ReadableStreamBYOBRequest/view}} will be transferred and no longer modifiable.
-
byobRequest.{{ReadableStreamBYOBRequest/respondWithNewView()|respondWithNewView}}(view)
+ Indicates to the associated [=readable byte stream=] that instead of writing into + {{ReadableStreamBYOBRequest/view}}, the [=underlying byte source=] is providing a new + {{ArrayBufferView}}, which will be given to the [=consumer=] of the [=readable byte stream=]. -
After this method is called, view will be transferred and no longer modifiable. +
The results of this algorithm are not currently observable, but could become so if JavaScript eventually -adds weak references. But even without that factor, -implementations will likely want to include similar steps.
- -Other specifications ought to leave |forAuthorCode| as its default value of
+ false, unless they are planning to directly expose the resulting { value, done }
+ object to authors. See the note regarding
+ ReadableStreamCreateReadResult for more information.
+
- readableStream.pipeTo(writableStream)
- .then(() => console.log("All data successfully written!"))
- .catch(e => console.error("Something went wrong!", e));
-
+ The same usage note for the |forAuthorCode| parameter applies here as it does for + [$AcquireReadableStreamBYOBReader$].
This abstract operation will throw an exception if and only if the supplied + |startAlgorithm| throws. +
- function writeArrayToStream(array, writableStream) {
- const writer = writableStream.getWriter();
- array.forEach(chunk => writer.write(chunk).catch(() => {}));
+
+ CreateReadableByteStream(|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|[,
+ |highWaterMark|, [, |autoAllocateChunkSize|]]) is meant to be called from other
+ specifications that wish to create {{ReadableStream}} instances that represent [=readable byte
+ streams=]. The |pullAlgorithm| and |cancelAlgorithm| algorithms must return promises; if supplied,
+ |highWaterMark| must be a non-negative, non-NaN number, and, if supplied, |autoAllocateChunkSize|
+ must be a positive integer.
+
+ It performs the following steps:
+
+ 1. If |highWaterMark| was not passed, set it to 0.
+ 1. If |autoAllocateChunkSize| was not passed, set it to undefined.
+ 1. Assert: ! [$IsNonNegativeNumber$](|highWaterMark|) is true.
+ 1. If |autoAllocateChunkSize| is not undefined,
+ 1. Assert: ! [$IsInteger$](|autoAllocateChunkSize|) is true.
+ 1. Assert: |autoAllocateChunkSize| is positive.
+ 1. Let |stream| be a [=new=] {{ReadableStream}}.
+ 1. Perform ! [$InitializeReadableStream$](|stream|).
+ 1. Let |controller| be a [=new=] {{ReadableByteStreamController}}.
+ 1. Perform ? [$SetUpReadableByteStreamController$](|stream|, |controller|, |startAlgorithm|,
+ |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, |autoAllocateChunkSize|).
+ 1. Return |stream|.
+
+ This abstract operation will throw an exception if and only if the supplied
+ |startAlgorithm| throws.
+
- return writer.close();
- }
+
+ InitializeReadableStream(|stream|) performs the following
+ steps:
- writeArrayToStream([1, 2, 3, 4, 5], writableStream)
- .then(() => console.log("All done!"))
- .catch(e => console.error("Error with the stream: " + e));
-
+ 1. Set |stream|.\[[state]] to "`readable`".
+ 1. Set |stream|.\[[reader]] and |stream|.\[[storedError]] to undefined.
+ 1. Set |stream|.\[[disturbed]] to false.
+
+
+
+ IsReadableStreamDisturbed(|stream|) is meant to be called from other specifications
+ that wish to query whether or not a readable stream has ever been read from or canceled. It
+ performs the following steps:
- Note how we use .catch(() => {})
to suppress any rejections from the
- {{WritableStreamDefaultWriter/write()}} method; we'll be notified of any fatal errors via a rejection of the
- {{WritableStreamDefaultWriter/close()}} method, and leaving them un-caught would cause potential
- {{unhandledrejection}} events and console warnings.
+ 1. Assert: |stream| [=implements=] {{ReadableStream}}.
+ 1. Return |stream|.\[[disturbed]].
-
- In the previous example we only paid attention to the success or failure of the entire stream, by looking at the
- promise returned by the writer's {{WritableStreamDefaultWriter/close()}} method. That promise will reject if anything
- goes wrong with the stream—initializing it, writing to it, or closing it. And it will fulfill once the stream is
- successfully closed. Often this is all you care about.
+
+ IsReadableStreamLocked(|stream|) is meant to be called from other specifications
+ that wish to query whether or not a readable stream is [=locked to a reader=].
- However, if you care about the success of writing a specific chunk, you can use the promise returned by the
- writer's {{WritableStreamDefaultWriter/write()}} method:
+ 1. Assert: |stream| [=implements=] {{ReadableStream}}.
+ 1. If |stream|.\[[reader]] is undefined, return false.
+ 1. Return true.
+
-
- writer.write("i am a chunk of data")
- .then(() => console.log("chunk successfully written!"))
- .catch(e => console.error(e));
-
+
+ ReadableStreamTee(|stream|,
+ |cloneForBranch2|) is meant to be called from other specifications that wish to [=tee a
+ readable stream|tee=] a given readable stream.
+
+ The second argument, |cloneForBranch2|, governs whether or not the data from the original stream
+ will be cloned (using HTML's [=serializable objects=] framework) before appearing in the second of
+ the returned branches. This is useful for scenarios where both branches are to be consumed in such
+ a way that they might otherwise interfere with each other, such as by [=transferable
+ objects|transferring=] their [=chunks=]. However, it does introduce a noticeable asymmetry between
+ the two branches, and limits the possible [=chunks=] to serializable ones. [[!HTML]]
+
+ In this standard ReadableStreamTee is always called with |cloneForBranch2| set to
+ false; other specifications pass true.
+
+ It performs the following steps:
+
+ 1. Assert: |stream| [=implements=] {{ReadableStream}}.
+ 1. Assert: |cloneForBranch2| is a boolean.
+ 1. Let |reader| be ? [$AcquireReadableStreamDefaultReader$](|stream|).
+ 1. Let |reading| be false.
+ 1. Let |canceled1| be false.
+ 1. Let |canceled2| be false.
+ 1. Let |reason1| be undefined.
+ 1. Let |reason2| be undefined.
+ 1. Let |branch1| be undefined.
+ 1. Let |branch2| be undefined.
+ 1. Let |cancelPromise| be [=a new promise=].
+ 1. Let |pullAlgorithm| be the following steps:
+ 1. If |reading| is true, return [=a promise resolved with=] undefined.
+ 1. Set |reading| to true.
+ 1. Let |readPromise| be the result of [=reacting=] to !
+ [$ReadableStreamDefaultReaderRead$](|reader|) with the following fulfillment steps given the
+ argument |result|:
+ 1. Set |reading| to false.
+ 1. Assert: [$Type$](|result|) is Object.
+ 1. Let |done| be ! [$Get$](|result|, "`done`").
+ 1. Assert: [$Type$](|done|) is Boolean.
+ 1. If |done| is true,
+ 1. If |canceled1| is false,
+ 1. Perform ! [$ReadableStreamDefaultControllerClose$](|branch1|.\[[readableStreamController]]).
+ 1. If |canceled2| is false,
+ 1. Perform !
+ [$ReadableStreamDefaultControllerClose$](|branch2|.\[[readableStreamController]]).
+ 1. Return.
+ 1. Let |value| be ! [$Get$](|result|, "`value`").
+ 1. Let |value1| and |value2| be |value|.
+ 1. If |canceled2| is false and |cloneForBranch2| is true, set |value2| to ?
+ [$StructuredDeserialize$](? [$StructuredSerialize$](|value2|), [=the current Realm=]).
+ 1. If |canceled1| is false, perform ?
+ [$ReadableStreamDefaultControllerEnqueue$](|branch1|.\[[readableStreamController]],
+ |value1|).
+ 1. If |canceled2| is false, perform ?
+ ReadableStreamDefaultControllerEnqueue(|branch2|.\[[readableStreamController]], |value2|).
+ 1. Set |readPromise|.\[[PromiseIsHandled]] to true.
+ 1. Return [=a promise resolved with=] undefined.
+ 1. Let |cancel1Algorithm| be the following steps, taking a |reason| argument:
+ 1. Set |canceled1| to true.
+ 1. Set |reason1| to |reason|.
+ 1. If |canceled2| is true,
+ 1. Let |compositeReason| be ! [$CreateArrayFromList$](« |reason1|, |reason2| »).
+ 1. Let |cancelResult| be ! [$ReadableStreamCancel$](|stream|, |compositeReason|).
+ 1. [=Resolve=] |cancelPromise| with |cancelResult|.
+ 1. Return |cancelPromise|.
+ 1. Let |cancel2Algorithm| be the following steps, taking a |reason| argument:
+ 1. Set |canceled2| to true.
+ 1. Set |reason2| to |reason|.
+ 1. If |canceled1| is true,
+ 1. Let |compositeReason| be ! [$CreateArrayFromList$](« |reason1|, |reason2| »).
+ 1. Let |cancelResult| be ! [$ReadableStreamCancel$](|stream|, |compositeReason|).
+ 1. [=Resolve=] |cancelPromise| with |cancelResult|.
+ 1. Return |cancelPromise|.
+ 1. Let |startAlgorithm| be an algorithm that returns undefined.
+ 1. Set |branch1| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|,
+ |cancel1Algorithm|).
+ 1. Set |branch2| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|,
+ |cancel2Algorithm|).
+ 1. [=Upon rejection=] of |reader|.\[[closedPromise]] with reason |r|,
+ 1. Perform ! [$ReadableStreamDefaultControllerError$](|branch1|.\[[readableStreamController]],
+ |r|).
+ 1. Perform ! [$ReadableStreamDefaultControllerError$](|branch2|.\[[readableStreamController]],
+ |r|).
+ 1. Return « |branch1|, |branch2| ».
+
- What "success" means is up to a given stream instance (or more precisely, its underlying sink) to decide. For
- example, for a file stream it could simply mean that the OS has accepted the write, and not necessarily that the
- chunk has been flushed to disk. Some streams might not be able to give such a signal at all, in which case the
- returned promise will fulfill immediately.
+
+ ReadableStreamPipeTo(|source|, |dest|, |preventClose|, |preventAbort|, |preventCancel|[,
+ |signal|]) is meant to be called from other specifications that wish to [=piping|pipe=] a
+ given readable stream to a destination [=writable stream=]. It performs the following steps:
+
+ 1. Assert: |source| [=implements=] {{ReadableStream}}.
+ 1. Assert: |dest| [=implements=] {{WritableStream}}.
+ 1. Assert: |preventClose|, |preventAbort|, and |preventCancel| are all booleans.
+ 1. If |signal| is not given, let |signal| be undefined.
+ 1. Assert: either |signal| is undefined, or |signal| [=implements=] {{AbortSignal}}.
+ 1. Assert: ! [$IsReadableStreamLocked$](|source|) is false.
+ 1. Assert: ! [$IsWritableStreamLocked$](|dest|) is false.
+ 1. If |source|.\[[readableStreamController]] [=implements=] {{ReadableByteStreamController}}, let
+ |reader| be either ! [$AcquireReadableStreamBYOBReader$](|source|) or !
+ [$AcquireReadableStreamDefaultReader$](|source|), at the user agent's discretion.
+ 1. Otherwise, let |reader| be ! [$AcquireReadableStreamDefaultReader$](|source|).
+ 1. Let |writer| be ! [$AcquireWritableStreamDefaultWriter$](|dest|).
+ 1. Set |source|.\[[disturbed]] to true.
+ 1. Let |shuttingDown| be false.
+ 1. Let |promise| be [=a new promise=].
+ 1. If |signal| is not undefined,
+ 1. Let |abortAlgorithm| be the following steps:
+ 1. Let |error| be a new "{{AbortError}}" {{DOMException}}.
+ 1. Let |actions| be an empty [=ordered set=].
+ 1. If |preventAbort| is false, [=set/append=] the following action to |actions|:
+ 1. If |dest|.\[[state]] is "`writable`", return ! [$WritableStreamAbort$](|dest|, |error|).
+ 1. Otherwise, return [=a promise resolved with=] undefined.
+ 1. If |preventCancel| is false, [=set/append=] the following action action to |actions|:
+ 1. If |source|.\[[state]] is "`readable`", return ! [$ReadableStreamCancel$](|source|, |error|).
+ 1. Otherwise, return [=a promise resolved with=] undefined.
+ 1. [=Shutdown with an action=] consisting of [=getting a promise to wait for all=] of the actions
+ in |actions|, and with |error|.
+ 1. If |signal|'s [=AbortSignal/aborted flag=] is set, perform |abortAlgorithm| and return
+ |promise|.
+ 1. [=AbortSignal/Add=] |abortAlgorithm| to |signal|.
+ 1. [=In parallel=] but not really; see #905, using |reader| and
+ |writer|, read all [=chunks=] from |source| and write them to |dest|. Due to the locking
+ provided by the reader and writer, the exact manner in which this happens is not observable to
+ author code, and so there is flexibility in how this is done. The following constraints apply
+ regardless of the exact algorithm used:
+ * Public API must not be used: while reading or writing, or performing any of
+ the operations below, the JavaScript-modifiable reader, writer, and stream APIs (i.e. methods
+ on the appropriate prototypes) must not be used. Instead, the streams must be manipulated
+ directly.
+ * Backpressure must be enforced:
+ * While [$WritableStreamDefaultWriterGetDesiredSize$](|writer|) is ≤ 0 or is null, the user
+ agent must not read from |reader|.
+ * If |reader| is a [=BYOB reader=], [$WritableStreamDefaultWriterGetDesiredSize$](|writer|)
+ should be used as a basis to determine the size of the chunks read from |reader|.
+ It's frequently inefficient to read chunks that are too small or too large.
+ Other information might be factored in to determine the optimal chunk size.
+ * Reads or writes should not be delayed for reasons other than these backpressure signals.
+
An implementation that waits for each write
+ to successfully complete before proceeding to the next read/write operation violates this
+ recommendation. In doing so, such an implementation makes the [=internal queue=] of |dest|
+ useless, as it ensures |dest| always contains at most one queued [=chunk=].
+ * Shutdown must stop activity: if |shuttingDown| becomes true, the user agent
+ must not initiate further reads from |reader|, and must only perform writes of already-read
+ [=chunks=], as described below. In particular, the user agent must check the below conditions
+ before performing any reads or writes, since they might lead to immediate shutdown.
+ * Error and close states must be propagated: the following conditions must be
+ applied in order.
+ 1. Errors must be propagated forward: if |source|.\[[state]] is or becomes
+ "`errored`", then
+ 1. If |preventAbort| is false, [=shutdown with an action=] of ! [$WritableStreamAbort$](|dest|,
+ |source|.\[[storedError]]) and with |source|.\[[storedError]].
+ 1. Otherwise, [=shutdown=] with |source|.\[[storedError]].
+ 1. Errors must be propagated backward: if |dest|.\[[state]] is or becomes
+ "`errored`", then
+ 1. If |preventCancel| is false, [=shutdown with an action=] of !
+ [$ReadableStreamCancel$](|source|, |dest|.\[[storedError]]) and with |dest|.\[[storedError]].
+ 1. Otherwise, [=shutdown=] with |dest|.\[[storedError]].
+ 1. Closing must be propagated forward: if |source|.\[[state]] is or becomes
+ "`closed`", then
+ 1. If |preventClose| is false, [=shutdown with an action=] of !
+ [$WritableStreamDefaultWriterCloseWithErrorPropagation$](|writer|).
+ 1. Otherwise, [=shutdown=].
+ 1. Closing must be propagated backward: if !
+ [$WritableStreamCloseQueuedOrInFlight$](|dest|) is true or |dest|.\[[state]] is "`closed`",
+ then
+ 1. Assert: no [=chunks=] have been read or written.
+ 1. Let |destClosed| be a new {{TypeError}}.
+ 1. If |preventCancel| is false, [=shutdown with an action=] of !
+ [$ReadableStreamCancel$](|source|, |destClosed|) and with |destClosed|.
+ 1. Otherwise, [=shutdown=] with |destClosed|.
+ * Shutdown with an action: if any of the
+ above requirements ask to shutdown with an action |action|, optionally with an error
+ |originalError|, then:
+ 1. If |shuttingDown| is true, abort these substeps.
+ 1. Set |shuttingDown| to true.
+ 1. If |dest|.\[[state]] is "`writable`" and ! [$WritableStreamCloseQueuedOrInFlight$](|dest|) is
+ false,
+ 1. If any [=chunks=] have been read but not yet written, write them to |dest|.
+ 1. Wait until every [=chunk=] that has been read has been written (i.e. the corresponding
+ promises have settled).
+ 1. Let |p| be the result of performing |action|.
+ 1. [=Upon fulfillment=] of |p|, [=finalize=], passing along |originalError| if it was given.
+ 1. [=Upon rejection=] of |p| with reason |newError|, [=finalize=] with |newError|.
+ * Shutdown: if any of the above requirements or steps
+ ask to shutdown, optionally with an error |error|, then:
+ 1. If |shuttingDown| is true, abort these substeps.
+ 1. Set |shuttingDown| to true.
+ 1. If |dest|.\[[state]] is "`writable`" and ! [$WritableStreamCloseQueuedOrInFlight$](|dest|) is
+ false,
+ 1. If any [=chunks=] have been read but not yet written, write them to |dest|.
+ 1. Wait until every [=chunk=] that has been read has been written (i.e. the corresponding
+ promises have settled).
+ 1. [=Finalize=], passing along |error| if it was given.
+ * Finalize: both forms of shutdown will eventually ask
+ to finalize, optionally with an error |error|, which means to perform the following steps:
+ 1. Perform ! [$WritableStreamDefaultWriterRelease$](|writer|).
+ 1. Perform ! [$ReadableStreamReaderGenericRelease$](|reader|).
+ 1. If |signal| is not undefined, [=AbortSignal/remove=] |abortAlgorithm| from |signal|.
+ 1. If |error| was given, [=reject=] |promise| with |error|.
+ 1. Otherwise, [=resolve=] |promise| with undefined.
+ 1. Return |promise|.
-
- The {{WritableStreamDefaultWriter/desiredSize}} and {{WritableStreamDefaultWriter/ready}} properties of writable
- stream writers allow producers to more precisely respond to flow control signals from the stream, to keep
- memory usage below the stream's specified high water mark. The following example writes an infinite sequence of
- random bytes to a stream, using {{WritableStreamDefaultWriter/desiredSize}} to determine how many bytes to generate at
- a given time, and using {{WritableStreamDefaultWriter/ready}} to wait for the backpressure to subside.
+Various abstract operations performed here include object creation (often of
+promises), which usually would require specifying a realm for the created object. However, because
+of the locking, none of these objects can be observed by author code. As such, the realm used to
+create them does not matter.
-
- async function writeRandomBytesForever(writableStream) {
- const writer = writableStream.getWriter();
+Interfacing with controllers
- while (true) {
- await writer.ready;
+In terms of specification factoring, the way that the {{ReadableStream}} class encapsulates the
+behavior of both simple readable streams and [=readable byte streams=] into a single class is by
+centralizing most of the potentially-varying logic inside the two controller classes,
+{{ReadableStreamDefaultController}} and {{ReadableByteStreamController}}. Those classes define most
+of the stateful internal slots and abstract operations for how a stream's [=internal queue=] is
+managed and how it interfaces with its [=underlying source=] or [=underlying byte source=].
- const bytes = new Uint8Array(writer.desiredSize);
- crypto.getRandomValues(bytes);
+Each controller class defines two internal methods, which are called by the {{ReadableStream}}
+algorithms:
- // Purposefully don't await; awaiting writer.ready is enough.
- writer.write(bytes).catch(() => {});
- }
- }
+
+ - \[[CancelSteps]](reason)
+
- The controller's steps that run in reaction to the stream being [=cancel a readable
+ stream|canceled=], used to clean up the state stored in the controller and inform the
+ [=underlying source=].
+
+
- \[[PullSteps]]()
+
- The controller's steps that run when a [=default reader=] is read from, used to pull from the
+ controller any queued [=chunks=], or pull from the [=underlying source=] to get more chunks.
+
+
+(These are defined as internal methods, instead of as abstract operations, so that they can be
+called polymorphically by the {{ReadableStream}} algorithms, without having to branch on which type
+of controller is present.)
+
+The rest of this section concerns abstract operations that go in the other direction: they are
+used by the controller implementations to affect their associated {{ReadableStream}} object. This
+translates internal state changes of the controller into developer-facing results visible through
+the {{ReadableStream}}'s public API.
+
+
+ ReadableStreamAddReadIntoRequest(|stream|)
+ performs the following steps:
+
+ 1. Assert: |stream|.\[[reader]] [=implements=] {{ReadableStreamBYOBReader}}.
+ 1. Assert: |stream|.\[[state]] is "`readable"` or `"closed`".
+ 1. Let |promise| be [=a new promise=].
+ 1. Let |readIntoRequest| be Record {\[[promise]]: |promise|}.
+ 1. [=list/Append=] |readIntoRequest| to |stream|.\[[reader]].\[[readIntoRequests]].
+ 1. Return |promise|.
+
- writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e));
-
+
+ ReadableStreamAddReadRequest(|stream|)
+ performs the following steps:
+
+ 1. Assert: |stream|.\[[reader]] [=implements=] {{ReadableStreamDefaultReader}}.
+ 1. Assert: |stream|.\[[state]] is "`readable"`.
+ 1. Let |promise| be [=a new promise=].
+ 1. Let |readIntoRequest| be Record {\[[promise]]: |promise|}.
+ 1. [=list/Append=] |readIntoRequest| to |stream|.\[[reader]].\[[readRequests]].
+ 1. Return |promise|.
+
- Note how we don't await
the promise returned by {{WritableStreamDefaultWriter/write()}}; this would be
- redundant with await
ing the {{WritableStreamDefaultWriter/ready}} promise. Additionally, similar to a previous example, we use the .catch(() => {})
pattern on the
- promises returned by {{WritableStreamDefaultWriter/write()}}; in this case we'll be notified about any failures
- await
ing the {{WritableStreamDefaultWriter/ready}} promise.
+
+ ReadableStreamCancel(|stream|, |reason|) performs the following steps:
+
+ 1. Set |stream|.\[[disturbed]] to true.
+ 1. If |stream|.\[[state]] is "`closed`", return [=a promise resolved with=] undefined.
+ 1. If |stream|.\[[state]] is "`errored`", return [=a promise rejected with=]
+ |stream|.\[[storedError]].
+ 1. Perform ! [$ReadableStreamClose$](|stream|).
+ 1. Let |sourceCancelPromise| be !
+ |stream|.\[[readableStreamController]].[$ReadableStreamController/[[CancelSteps]]$](|reason|).
+ 1. Return the result of [=reacting=] to |sourceCancelPromise| with a fulfillment step that returns
+ undefined.
-
- To further emphasize how it's a bad idea to await
the promise returned by
- {{WritableStreamDefaultWriter/write()}}, consider a modification of the above example, where we continue to use the
- {{WritableStreamDefaultWriter}} interface directly, but we don't control how many bytes we have to write at a given
- time. In that case, the backpressure-respecting code looks the same:
+
+ ReadableStreamClose(|stream|) performs the following steps:
+
+ 1. Assert: |stream|.\[[state]] is "`readable`".
+ 1. Set |stream|.\[[state]] to "`closed`".
+ 1. Let |reader| be |stream|.\[[reader]].
+ 1. If |reader| is undefined, return.
+ 1. If ! IsReadableStreamDefaultReader(|reader|) is true,
+ 1. [=list/For each=] |readRequest| of |reader|.\[[readRequests]],
+ 1. [=Resolve=] |readRequest|.\[[promise]] with ! [$ReadableStreamCreateReadResult$](undefined,
+ true, |reader|.\[[forAuthorCode]]).
+ 1. Set |reader|.\[[readRequests]] to an empty [=list=].
+ 1. [=Resolve=] |reader|.\[[closedPromise]] with undefined.
+
+ The case where |stream|.\[[state]] is "`closed`", but |stream|.\[[closeRequested]]
+ is false, will happen if the stream was closed without its controller's close method ever being
+ called: i.e., if the stream was closed by a call to {{ReadableStream/cancel(reason)}}. In this
+ case we allow the controller's close()
method to be called and silently do nothing,
+ since the cancelation was outside the control of the underlying source.
+
-
- async function writeSuppliedBytesForever(writableStream, getBytes) {
- const writer = writableStream.getWriter();
+
+ ReadableStreamCreateReadResult(|value|, |done|,
+ |forAuthorCode|) performs the following steps:
+
+ 1. Let |prototype| be null.
+ 1. If |forAuthorCode| is true, set |prototype| to {{%ObjectPrototype%}}.
+ 1. Assert: [$Type$](|done|) is Boolean.
+ 1. Let |obj| be [$OrdinaryObjectCreate$](|prototype|).
+ 1. Perform [$CreateDataProperty$](|obj|, "`value`", |value|).
+ 1. Perform [$CreateDataProperty$](|obj|, "`done`", |done|).
+ 1. Return |obj|.
+
+
+ When |forAuthorCode| is true, this abstract operation gives the same result as
+ [$CreateIterResultObject$](|value|, |done|). This provides the expected semantics when the object
+ is to be returned from the {{ReadableStreamDefaultReader/read()|defaultReader.read()}} or
+ {{ReadableStreamBYOBReader/read()|byobReader.read()}} methods.
- while (true) {
- await writer.ready;
+ However, resolving promises with such objects will unavoidably result in an access to
+ Object.prototype.then
. For internal use, particularly in {{ReadableStream/pipeTo()}}
+ and in other specifications, it is important that reads not be observable by author code—even if
+ that author code has tampered with
+ Object.prototype
. For this reason, a false value of |forAuthorCode| results in an
+ object with a null prototype, keeping promise resolution unobservable.
+
+ The underlying issue here is that reading from streams always uses promises for { value,
+ done }
objects, even in specifications. Although it is conceivable we could rephrase all
+ of the internal algorithms to not use promises and note use JavaScript objects, and instead only
+ package up the results into promise-for-{ value, done }
when a read()
+ method is called, this would be a large undertaking, which we have not done. See
+ whatwg/infra#181 for more background on
+ this subject.
+
+
- const bytes = getBytes();
- writer.write(bytes).catch(() => {});
- }
- }
-
+
+ ReadableStreamError(|stream|,
+ |e|) performs the following steps:
+
+ 1. Assert: |stream|.\[[reader]] [=implements=] {{ReadableStreamDefaultReader}}.
+ 1. Assert: |stream|.\[[state]] is "`readable`".
+ 1. Set |stream|.\[[state]] to "`errored`".
+ 1. Set |stream|.\[[storedError]] to |e|.
+ 1. Let |reader| be |stream|.\[[reader]].
+ 1. If |reader| is undefined, return.
+ 1. If |reader| [=implements=] {{ReadableStreamDefaultReader}},
+ 1. [=list/For each=] |readRequest| of |reader|.\[[readRequests]],
+ 1. [=Reject=] |readRequest|.\[[promise]] with |e|.
+ 1. Set |reader|.\[[readRequests]] to a new empty [=list=].
+ 1. Otherwise,
+ 1. Assert: |reader| [=implements=] {{ReadableStreamBYOBReader}}.
+ 1. [=list/For each=] |readIntoRequest| of |reader|.\[[readIntoRequests]],
+ 1. [=Reject=] |readIntoRequest|.\[[promise]] with |e|.
+ 1. Set |reader|.\[[readIntoRequests]] to a new empty [=list=].
+ 1. [=Reject=] |reader|.\[[closedPromise]] with |e|.
+ 1. Set |reader|.\[[closedPromise]].\[[PromiseIsHandled]] to true.
+
- Unlike the previous example, where—because we were always writing exactly
- {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} bytes each time—the
- {{WritableStreamDefaultWriter/write()}} and {{WritableStreamDefaultWriter/ready}} promises were synchronized, in this
- case it's quite possible that the {{WritableStreamDefaultWriter/ready}} promise fulfills before the one returned by
- {{WritableStreamDefaultWriter/write()}} does. Remember, the {{WritableStreamDefaultWriter/ready}} promise fulfills
- when the desired size becomes positive, which might be
- before the write succeeds (especially in cases with a larger high water mark).
+
+ ReadableStreamFulfillReadIntoRequest(|stream|,
+ |chunk|, |done|) performs the following steps:
- In other words, await
ing the return value of {{WritableStreamDefaultWriter/write()}} means you never
- queue up writes in the stream's internal queue, instead only executing a write after the previous one succeeds,
- which can result in low throughput.
+ 1. Let |reader| be |stream|.\[[reader]].
+ 1. Let |readIntoRequest| be |reader|.\[[readIntoRequests]][0].
+ 1. [=list/Remove=] |readIntoRequest| from |reader|.\[[readIntoRequests]].
+ 1. [=Resolve=] |readIntoRequest|.\[[promise]] with ! [$ReadableStreamCreateReadResult$](|chunk|,
+ |done|, |reader|.\[[forAuthorCode]]).
-Class WritableStream
+
+ ReadableStreamFulfillReadRequest(|stream|, |chunk|,
+ |done|) performs the following steps:
-Class definition
+ 1. Let |reader| be |stream|.\[[reader]].
+ 1. Let |readRequest| be |reader|.\[[readRequests]][0].
+ 1. [=list/Remove=] |readRequest| from |reader|.\[[readRequests]].
+ 1. [=Resolve=] |readRequest|.\[[promise]] with ! [$ReadableStreamCreateReadResult$](|chunk|,
+ |done|, |reader|.\[[forAuthorCode]]).
+
-
+
+ ReadableStreamGetNumReadIntoRequests(|stream|)
+ performs the following steps:
-This section is non-normative.
+ 1. Return |stream|.\[[reader]].\[[readIntoRequests]]'s [=list/size=].
+
+
+
+ ReadableStreamGetNumReadRequests(|stream|)
+ performs the following steps:
-If one were to write the {{WritableStream}} class in something close to the syntax of [[!ECMASCRIPT]], it would look
-like
+ 1. Return |stream|.\[[reader]].\[[readRequests]]'s [=list/size=].
+
-
- class WritableStream {
- constructor(underlyingSink = {}, strategy = {})
+
+ ReadableStreamHasBYOBReader(|stream|) performs the
+ following steps:
- get locked()
+ 1. Let |reader| be |stream|.\[[reader]].
+ 1. If |reader| is undefined, return false.
+ 1. If |reader| [=implements=] {{ReadableStreamBYOBReader}}, return true.
+ 1. Return false.
+
- abort(reason)
- close()
- getWriter()
- }
-
+
+ ReadableStreamHasDefaultReader(|stream|) performs the
+ following steps:
+ 1. Let |reader| be |stream|.\[[reader]].
+ 1. If |reader| is undefined, return false.
+ 1. If |reader| [=implements=] {{ReadableStreamDefaultReader}}, return true.
+ 1. Return false.
-Internal slots
+Readers
-Instances of {{WritableStream}} are created with the internal slots described in the following table:
+The following abstract operations support the implementation and manipulation of
+{{ReadableStreamDefaultReader}} and {{ReadableStreamBYOBReader}} instances.
-
-
-
- Internal Slot
- Description (non-normative)
-
-
-
- \[[backpressure]]
- The backpressure signal set by the controller
-
-
- \[[closeRequest]]
- The promise returned from the writer {{WritableStreamDefaultWriter/close()}} method
-
-
- \[[inFlightWriteRequest]]
- A slot set to the promise for the current in-flight write operation while the
- underlying sink's write algorithm is executing and has not yet fulfilled, used to prevent reentrant calls
-
-
- \[[inFlightCloseRequest]]
- A slot set to the promise for the current in-flight close operation while the
- underlying sink's close algorithm is executing and has not yet fulfilled, used to prevent the
- {{WritableStreamDefaultWriter/abort()}} method from interrupting close
-
-
- \[[pendingAbortRequest]]
- A Record containing the promise returned from
- {{WritableStreamDefaultWriter/abort()}} and the reason passed to
- {{WritableStreamDefaultWriter/abort()}}
-
-
- \[[state]]
- A string containing the stream's current state, used internally; one of
- "writable"
, "closed"
, "erroring"
, or "errored"
-
-
- \[[storedError]]
- A value indicating how the stream failed, to be given as a failure reason or exception
- when trying to operate on the stream while in the "errored"
state
-
-
- \[[writableStreamController]]
- A {{WritableStreamDefaultController}} created with the ability to control the state and
- queue of this stream; also used for the IsWritableStream brand check
-
-
- \[[writer]]
- A {{WritableStreamDefaultWriter}} instance, if the stream is locked to a writer, or
- undefined if it is not
-
-
- \[[writeRequests]]
- A List of promises representing the stream's internal queue of write requests not yet
- processed by the underlying sink
-
-
+
+ ReadableStreamReaderGenericCancel(|reader|,
+ |reason|) performs the following steps:
-
- The \[[inFlightCloseRequest]] slot and \[[closeRequest]] slot are mutually exclusive. Similarly, no element will be
- removed from \[[writeRequests]] while \[[inFlightWriteRequest]] is not undefined . Implementations
- can optimize storage for these slots based on these invariants.
-
+ 1. Let |stream| be |reader|.\[[ownerReadableStream]].
+ 1. Assert: |stream| is not undefined.
+ 1. Return ! [$ReadableStreamCancel$](|stream|, |reason|).
+
-new
-WritableStream(underlyingSink = {}, strategy = {})
+
+ ReadableStreamReaderGenericInitialize(|reader|,
+ |stream|) performs the following steps:
+
+ 1. Set |reader|.\[[forAuthorCode]] to true.
+ 1. Set |reader|.\[[ownerReadableStream]] to |stream|.
+ 1. Set |stream|.\[[reader]] to |reader|.
+ 1. If |stream|.\[[state]] is "`readable`",
+ 1. Set |reader|.\[[closedPromise]] to [=a new promise=].
+ 1. Otherwise, if |stream|.\[[state]] is "`closed`",
+ 1. Set |reader|.\[[closedPromise]] to [=a promise resolved with=] undefined.
+ 1. Otherwise,
+ 1. Assert: |stream|.\[[state]] is "`errored`".
+ 1. Set |reader|.\[[closedPromise]] to [=a promise rejected with=] |stream|.\[[storedError]].
+ 1. Set |reader|.\[[closedPromise]].\[[PromiseIsHandled]] to true.
+
-
- The underlyingSink
argument represents the underlying sink, as described in
- [[#underlying-sink-api]].
-
- The strategy
argument represents the stream's queuing strategy, as described in [[#qs-api]]. If it
- is not provided, the default behavior will be the same as a {{CountQueuingStrategy}} with a high water mark
- of 1.
-
-
-
- 1. Perform ! InitializeWritableStream(_this_).
- 1. Let _size_ be ? GetV(_strategy_, `"size"`).
- 1. Let _highWaterMark_ be ? GetV(_strategy_, `"highWaterMark"`).
- 1. Let _type_ be ? GetV(_underlyingSink_, `"type"`).
- 1. If _type_ is not *undefined*, throw a *RangeError* exception. This is to allow us to add new
- potential types in the future, without backward-compatibility concerns.
- 1. Let _sizeAlgorithm_ be ? MakeSizeAlgorithmFromSizeFunction(_size_).
- 1. If _highWaterMark_ is *undefined*, let _highWaterMark_ be *1*.
- 1. Set _highWaterMark_ to ? ValidateAndNormalizeHighWaterMark(_highWaterMark_).
- 1. Perform ? SetUpWritableStreamDefaultControllerFromUnderlyingSink(*this*, _underlyingSink_, _highWaterMark_,
- _sizeAlgorithm_).
-
-
-Underlying sink API
+
+ ReadableStreamReaderGenericRelease(|reader|)
+ performs the following steps:
+
+ 1. Assert: |reader|.\[[ownerReadableStream]] is not undefined.
+ 1. Assert: |reader|.\[[ownerReadableStream]].\[[reader]] is |reader|.
+ 1. If |reader|.\[[ownerReadableStream]].\[[state]] is "`readable`", [=reject=]
+ |reader|.\[[closedPromise]] with a {{TypeError}} exception.
+ 1. Otherwise, set |reader|.\[[closedPromise]] to [=a promise rejected with=] a {{TypeError}}
+ exception.
+ 1. Set |reader|.\[[closedPromise]].\[[PromiseIsHandled]] to true.
+ 1. Set |reader|.\[[ownerReadableStream]].\[[reader]] to undefined.
+ 1. Set |reader|.\[[ownerReadableStream]] to undefined.
+
-
+
+ ReadableStreamBYOBReaderRead(|reader|, |view|) performs
+ the following steps:
+
+ 1. Let |stream| be |reader|.\[[ownerReadableStream]].
+ 1. Assert: |stream| is not undefined.
+ 1. Set |stream|.\[[disturbed]] to true.
+ 1. If |stream|.\[[state]] is "`errored`", return [=a promise rejected with=]
+ |stream|.\[[storedError]].
+ 1. Return ! [$ReadableByteStreamControllerPullInto$](|stream|.\[[readableStreamController]],
+ |view|).
+
-This section is non-normative.
+
+ ReadableStreamDefaultReaderRead(|reader|) performs
+ the following steps:
+
+ 1. Let |stream| be |reader|.\[[ownerReadableStream]].
+ 1. Assert: |stream| is not undefined.
+ 1. Set |stream|.\[[disturbed]] to true.
+ 1. If |stream|.\[[state]] is "`closed`", return [=a promise resolved with=] !
+ [$ReadableStreamCreateReadResult$](undefined, true, |reader|.\[[forAuthorCode]]).
+ 1. If |stream|.\[[state]] is "`errored`", return [=a promise rejected with=]
+ |stream|.\[[storedError]].
+ 1. Assert: |stream|.\[[state]] is "`readable`".
+ 1. Return ! |stream|.\[[readableStreamController]].[$ReadableStreamController/[[PullSteps]]$]().
+
-The {{WritableStream()}} constructor accepts as its first argument a JavaScript object representing the underlying
-sink. Such objects can contain any of the following properties:
+
+ SetUpReadableStreamBYOBReader(|reader|, |stream|)
+ performs the following steps:
-
- - start(controller)
- -
-
A function that is called immediately during creation of the {{WritableStream}}.
+ 1. If ! [$IsReadableStreamLocked$](|stream|) is true, throw a {{TypeError}} exception.
+ 1. If ! |stream|.\[[readableStreamController]] does not [=implement=]
+ {{ReadableByteStreamController}}, throw a {{TypeError}} exception.
+ 1. Perform ! [$ReadableStreamReaderGenericInitialize$](|reader|, |stream|).
+ 1. Set |reader|.\[[readIntoRequests]] to a new empty [=list=].
+
- Typically this is used to acquire access to the underlying sink resource being represented.
+
+ SetUpReadableStreamDefaultReader(|reader|,
+ |stream|) performs the following steps:
- If this setup process is asynchronous, it can return a promise to signal success or failure; a rejected promise
- will error the stream. Any thrown exceptions will be re-thrown by the {{WritableStream()}} constructor.
-
+ 1. If ! [$IsReadableStreamLocked$](|stream|) is true, throw a {{TypeError}} exception.
+ 1. Perform ! [$ReadableStreamReaderGenericInitialize$](|reader|, |stream|).
+ 1. Set |reader|.\[[readRequests]] to a new empty [=list=].
+
- write(chunk, controller)
-
- A function that is called when a new chunk of data is ready to be written to the underlying sink.
- The stream implementation guarantees that this function will be called only after previous writes have succeeded,
- and never before {{underlying sink/start()}} has succeeded or after {{underlying sink/close()}} or
- {{underlying sink/abort()}} have been called.
-
- This function is used to actually send the data to the resource presented by the underlying sink, for
- example by calling a lower-level API.
-
- If the process of writing data is asynchronous, and communicates success or failure signals back to its user,
- then this function can return a promise to signal success or failure. This promise return value will be communicated
- back to the caller of {{WritableStreamDefaultWriter/write()|writer.write()}}, so they can monitor that individual
- write. Throwing an exception is treated the same as returning a rejected promise.
-
- Note that such signals are not always available; compare e.g. [[#example-ws-no-backpressure]] with
- [[#example-ws-backpressure]]. In such cases, it's best to not return anything.
-
- The promise potentially returned by this function also governs whether the given chunk counts as written for the
- purposes of computed the desired size to fill the stream's
- internal queue. That is, during the time it takes the promise to settle,
- {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} will stay at its previous value, only increasing to
- signal the desire for more chunks once the write succeeds.
-
-
- close()
-
- A function that is called after the producer signals, via
- {{WritableStreamDefaultWriter/close()|writer.close()}}, that they are done writing chunks to the stream, and
- subsequently all queued-up writes have successfully completed.
+Default controllers
+
+The following abstract operations support the implementation of the
+{{ReadableStreamDefaultController}} class.
+
+
+ ReadableStreamDefaultControllerCallPullIfNeeded(|controller|)
+ performs the following steps:
+
+ 1. Let |shouldPull| be ! [$ReadableStreamDefaultControllerShouldCallPull$](|controller|).
+ 1. If |shouldPull| is false, return.
+ 1. If |controller|.\[[pulling]] is true,
+ 1. Set |controller|.\[[pullAgain]] to true.
+ 1. Return.
+ 1. Assert: |controller|.\[[pullAgain]] is false.
+ 1. Set |controller|.\[[pulling]] to true.
+ 1. Let |pullPromise| be the result of performing |controller|.\[[pullAlgorithm]].
+ 1. [=Upon fulfillment=] of |pullPromise|,
+ 1. Set |controller|.\[[pulling]] to false.
+ 1. If |controller|.\[[pullAgain]] is true,
+ 1. Set |controller|.\[[pullAgain]] to false.
+ 1. Perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$](|controller|).
+ 1. [=Upon rejection=] of |pullPromise| with reason |e|,
+ 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |e|).
+
- This function can perform any actions necessary to finalize or flush writes to the underlying sink, and
- release access to any held resources.
+
+ ReadableStreamDefaultControllerShouldCallPull(|controller|)
+ performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is false, return false.
+ 1. If |controller|.\[[started]] is false, return false.
+ 1. If ! [$IsReadableStreamLocked$](|stream|) is true and !
+ [$ReadableStreamGetNumReadRequests$](|stream|) > 0, return true.
+ 1. Let |desiredSize| be ! [$ReadableStreamDefaultControllerGetDesiredSize$](|controller|).
+ 1. Assert: |desiredSize| is not null.
+ 1. If |desiredSize| > 0, return true.
+ 1. Return false.
+
- If the shutdown process is asynchronous, the function can return a promise to signal success or failure; the
- result will be communicated via the return value of the called
- {{WritableStreamDefaultWriter/close()|writer.close()}} method. Additionally, a rejected promise will error the
- stream, instead of letting it close successfully. Throwing an exception is treated the same as returning a rejected
- promise.
-
+
+ ReadableStreamDefaultControllerClearAlgorithms(|controller|)
+ is called once the stream is closed or errored and the algorithms will not be executed any more. By
+ removing the algorithm references it permits the [=underlying source=] object to be garbage
+ collected even if the {{ReadableStream}} itself is still referenced.
- abort(reason)
-
- A function that is called after the producer signals, via {{WritableStream/abort()|stream.abort()}} or
- {{WritableStreamDefaultWriter/abort()|writer.abort()}}, that they wish to abort
- the stream. It takes as its argument the same value as was passed to those methods by the producer.
+ This is observable using weak
+ references. See tc39/proposal-weakrefs#31 for more
+ detail.
-
Writable streams can additionally be aborted under certain conditions during piping; see the definition
- of the {{ReadableStream/pipeTo()}} method for more details.
+ It performs the following steps:
- This function can clean up any held resources, much like {{underlying sink/close()}}, but perhaps with some
- custom handling.
+ 1. Set |controller|.\[[pullAlgorithm]] to undefined.
+ 1. Set |controller|.\[[cancelAlgorithm]] to undefined.
+ 1. Set |controller|.\[[strategySizeAlgorithm]] to undefined.
+
- If the shutdown process is asynchronous, the function can return a promise to signal success or failure; the
- result will be communicated via the return value of the called abort()
method. Throwing an exception is
- treated the same as returning a rejected promise. Regardless, the stream will be errored with a new {{TypeError}}
- indicating that it was aborted.
-
-
+
+ ReadableStreamDefaultControllerClose(|controller|) can be called by other
+ specifications that wish to close a readable stream, in the same way a developer-created stream
+ would be closed by its associated controller object. Specifications should not do this to
+ streams or controllers they did not create.
+
+ It performs the following steps:
+
+ 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is false, return.
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. Set |controller|.\[[closeRequested]] to true.
+ 1. If |controller|.\[[queue]] [=list/is empty=],
+ 1. Perform ! [$ReadableStreamDefaultControllerClearAlgorithms$](|controller|).
+ 1. Perform ! [$ReadableStreamClose$](|stream|).
+
-The controller
argument passed to {{underlying sink/start()}} and {{underlying sink/write()}} is an
-instance of {{WritableStreamDefaultController}}, and has the ability to error the stream. This is mainly used for
-bridging the gap with non-promise-based APIs, as seen for example in [[#example-ws-no-backpressure]].
+
+ ReadableStreamDefaultControllerClose(|controller|, |chunk|) can be called by other
+ specifications that wish to enqueue [=chunks=] in a readable stream, in the same way a developer
+ would enqueue chunks using the stream's associated controller object. Specifications should
+ not do this to streams or controllers they did not create.
+
+ It performs the following steps:
+
+ 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is false, return.
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. If ! [$IsReadableStreamLocked$](|stream|) is true and !
+ [$ReadableStreamGetNumReadRequests$](|stream|) > 0, perform !
+ [$ReadableStreamFulfillReadRequest$](|stream|, |chunk|, false).
+ 1. Otherwise,
+ 1. Let |result| be the result of performing |controller|.\[[strategySizeAlgorithm]], passing in
+ |chunk|, and interpreting the result as a [=completion record=].
+ 1. If |result| is an abrupt completion,
+ 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |result|.\[[Value]]).
+ 1. Return |result|.
+ 1. Let |chunkSize| be |result|.\[[Value]].
+ 1. Let |enqueueResult| be [$EnqueueValueWithSize$](|controller|, |chunk|, |chunkSize|).
+ 1. If |enqueueResult| is an abrupt completion,
+ 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |enqueueResult|.\[[Value]]).
+ 1. Return |enqueueResult|.
+ 1. Perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$](|controller|).
+
+
+ ReadableStreamDefaultControllerError(|controller|, |e|) can be called by other
+ specifications that wish to move a readable stream to an errored state, in the same way a
+ developer would error a stream using its associated controller object. Specifications should
+ not do this to streams or controllers they did not create.
+
+ It performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. If |stream|.\[[state]] is not "`readable`", return.
+ 1. Perform ! [$ResetQueue$](|controller|).
+ 1. Perform ! [$ReadableStreamDefaultControllerClearAlgorithms$](|controller|).
+ 1. Perform ! [$ReadableStreamError$](|stream|, |e|).
-Properties of the {{WritableStream}} prototype
+
+ ReadableStreamDefaultControllerGetDesiredSize(|controller|) can be called by other
+ specifications that wish to determine the [=desired size to fill a stream's internal queue|desired
+ size to fill this stream's internal queue=], similar to how a developer would consult the
+ {{ReadableStreamDefaultController/desiredSize}} property of the stream's associated controller
+ object. Specifications should not use this on streams or controllers they did not create.
+
+ It performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. Let |state| be |stream|.\[[state]].
+ 1. If |state| is "`errored`", return null.
+ 1. If |state| is "`closed`", return 0.
+ 1. Return |controller|.\[[strategyHWM]] − |controller|.\[[queueTotalSize]].
+
-get locked
+
+ ReadableStreamDefaultControllerHasBackpressure(|controller|)
+ is used in the implementation of {{TransformStream}}. It performs the following steps:
-
- The locked
getter returns whether or not the writable stream is locked to a writer.
+ 1. If ! [$ReadableStreamDefaultControllerShouldCallPull$](|controller|) is true, return false.
+ 1. Otherwise, return true.
-
- 1. If ! IsWritableStream(*this*) is *false*, throw a *TypeError* exception.
- 1. Return ! IsWritableStreamLocked(*this*).
-
+
+ ReadableStreamDefaultControllerCanCloseOrEnqueue(|controller|)
+ performs the following steps:
+
+ 1. Let |state| be |controller|.\[[controlledReadableStream]].\[[state]].
+ 1. If |controller|.\[[closeRequested]] is false and |state| is "`readable`", return true.
+ 1. Otherwise, return false.
+
+ The case where |controller|.\[[closeRequested]] is false, but |state| is not
+ "`readable`", happens when the stream is errored via
+ {{ReadableStreamDefaultController/error(e)|controller.error()}}, or when it is closed without its
+ controller's {{ReadableStreamDefaultController/close()|controller.close()}} method ever being
+ called: e.g., if the stream was closed by a call to
+ {{ReadableStream/cancel(reason)|stream.cancel()}}.
+
-abort(reason)
+
+ SetUpReadableStreamDefaultController(|stream|,
+ |controller|, |startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|,
+ |sizeAlgorithm|) performs the following steps:
+
+ 1. Assert: |stream|.\[[readableStreamController]] is undefined.
+ 1. Set |controller|.\[[controlledReadableStream]] to |stream|.
+ 1. Set |controller|.\[[queue]] and |controller|.\[[queueTotalSize]] to undefined, then perform !
+ [$ResetQueue$](|controller|).
+ 1. Set |controller|.\[[started]], |controller|.\[[closeRequested]], |controller|.\[[pullAgain]], and
+ |controller|.\[[pulling]] to false.
+ 1. Set |controller|.\[[strategySizeAlgorithm]] to |sizeAlgorithm| and |controller|.\[[strategyHWM]]
+ to |highWaterMark|.
+ 1. Set |controller|.\[[pullAlgorithm]] to |pullAlgorithm|.
+ 1. Set |controller|.\[[cancelAlgorithm]] to |cancelAlgorithm|.
+ 1. Set |stream|.\[[readableStreamController]] to |controller|.
+ 1. Let |startResult| be the result of performing |startAlgorithm|. (This might throw an exception.)
+ 1. Let |startPromise| be [=a promise resolved with=] |startResult|.
+ 1. [=Upon fulfillment=] of |startPromise|,
+ 1. Set |controller|.\[[started]] to true.
+ 1. Assert: |controller|.\[[pulling]] is false.
+ 1. Assert: |controller|.\[[pullAgain]] is false.
+ 1. Perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$](|controller|).
+ 1. [=Upon rejection=] of |startPromise| with reason |r|,
+ 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |r|).
+
-
- The abort
method aborts the stream, signaling that the producer can
- no longer successfully write to the stream and it is to be immediately moved to an errored state, with any queued-up
- writes discarded. This will also execute any abort mechanism of the underlying sink.
+
+ SetUpReadableStreamDefaultControllerFromUnderlyingSource(|stream|,
+ |controller|, |underlyingSource|, |underlyingSourceDict|, |highWaterMark|, |sizeAlgorithm|)
+ performs the following steps:
+
+ 1. Let |controller| be a [=new=] {{ReadableStreamDefaultController}}.
+ 1. Let |startAlgorithm| be an algorithm that returns undefined.
+ 1. Let |pullAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined.
+ 1. Let |cancelAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined.
+ 1. If |underlyingSourceDict|["{{UnderlyingSource/start}}"] [=map/exists=], then set
+ |startAlgorithm| to an algorithm which returns the result of [=invoking=]
+ |underlyingSourceDict|["{{UnderlyingSource/start}}"] with argument list
+ « |controller| » and [=callback this value=] |underlyingSource|.
+ 1. If |underlyingSourceDict|["{{UnderlyingSource/pull}}"] [=map/exists=], then set
+ |pullAlgorithm| to an algorithm which returns the result of [=invoking=]
+ |underlyingSourceDict|["{{UnderlyingSource/pull}}"] with argument list
+ « |controller| » and [=callback this value=] |underlyingSource|.
+ 1. If |underlyingSourceDict|["{{UnderlyingSource/cancel}}"] [=map/exists=], then set
+ |cancelAlgorithm| to an algorithm which takes an argument |reason| and returns the result of
+ [=invoking=] |underlyingSourceDict|["{{UnderlyingSource/cancel}}"] with argument list
+ « |reason| » and [=callback this value=] |underlyingSource|.
+ 1. Perform ? [$SetUpReadableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|,
+ |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, |sizeAlgorithm|).
-
- 1. If ! IsWritableStream(*this*) is *false*, return a promise rejected with a *TypeError* exception.
- 1. If ! IsWritableStreamLocked(*this*) is *true*, return a promise rejected with a *TypeError* exception.
- 1. Return ! WritableStreamAbort(*this*, _reason_).
-
+Byte stream controllers
+
+
+ ReadableByteStreamControllerCallPullIfNeeded(|controller|)
+ performs the following steps:
+
+ 1. Let |shouldPull| be ! [$ReadableByteStreamControllerShouldCallPull$](|controller|).
+ 1. If |shouldPull| is false, return.
+ 1. If |controller|.\[[pulling]] is true,
+ 1. Set |controller|.\[[pullAgain]] to true.
+ 1. Return.
+ 1. Assert: |controller|.\[[pullAgain]] is false.
+ 1. Set |controller|.\[[pulling]] to true.
+ 1. Let |pullPromise| be the result of performing |controller|.\[[pullAlgorithm]].
+ 1. [=Upon fulfillment=] of |pullPromise|,
+ 1. Set |controller|.\[[pulling]] to false.
+ 1. If |controller|.\[[pullAgain]] is true,
+ 1. Set |controller|.\[[pullAgain]] to false.
+ 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|).
+ 1. [=Upon rejection=] of |pullPromise| with reason |e|,
+ 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |e|).
+
-close()
+
+ ReadableByteStreamControllerClearAlgorithms(|controller|)
+ is called once the stream is closed or errored and the algorithms will not be executed any more. By
+ removing the algorithm references it permits the [=underlying byte source=] object to be garbage
+ collected even if the {{ReadableStream}} itself is still referenced.
-
- The close
method closes the stream. The underlying sink will finish processing any
- previously-written chunks, before invoking its close behavior. During this time any further attempts to write
- will fail (without erroring the stream).
+ This is observable using weak
+ references. See tc39/proposal-weakrefs#31 for more
+ detail.
- The method returns a promise that is fulfilled with undefined if all remaining chunks are
- successfully written and the stream successfully closes, or rejects if an error is encountered during this process.
+ It performs the following steps:
+
+ 1. Set |controller|.\[[pullAlgorithm]] to undefined.
+ 1. Set |controller|.\[[cancelAlgorithm]] to undefined.
-
- 1. If ! IsWritableStream(*this*) is *false*, return a promise rejected with a *TypeError* exception.
- 1. If ! IsWritableStreamLocked(*this*) is *true*, return a promise rejected with a *TypeError* exception.
- 1. If ! WritableStreamCloseQueuedOrInFlight(*this*) is *true*, return a promise rejected with a *TypeError*
- exception.
- 1. Return ! WritableStreamClose(*this*).
-
+
+ ReadableByteStreamControllerClearPendingPullIntos(|controller|)
+ performs the following steps:
-getWriter()
+ 1. Perform ! [$ReadableByteStreamControllerInvalidateBYOBRequest$](|controller|).
+ 1. Set |controller|.\[[pendingPullIntos]] to a new empty [=list=].
+
-
- The getWriter
method creates a writer (an instance of {{WritableStreamDefaultWriter}}) and locks the stream to the new writer. While the stream is locked, no other writer can be
- acquired until this one is released.
-
- This functionality is especially useful for creating abstractions that desire the ability to write to a stream without
- interruption or interleaving. By getting a writer for the stream, you can ensure nobody else can write at the same
- time, which would cause the resulting written data to be unpredictable and probably useless.
-
-
-
- 1. If ! IsWritableStream(*this*) is *false*, throw a *TypeError* exception.
- 1. Return ? AcquireWritableStreamDefaultWriter(*this*).
-
-
-General writable stream abstract operations
-
-The following abstract operations, unlike most in this specification, are meant to be generally useful by other
-specifications, instead of just being part of the implementation of this spec's classes.
-
-AcquireWritableStreamDefaultWriter ( stream )
-
-
- 1. Return ? Construct(`WritableStreamDefaultWriter`, « _stream_ »).
-
-
-CreateWritableStream ( startAlgorithm,
-writeAlgorithm, closeAlgorithm, abortAlgorithm [, highWaterMark [,
-sizeAlgorithm ] ] )
-
-This abstract operation is meant to be called from other specifications that wish to create {{WritableStream}}
-instances. The writeAlgorithm, closeAlgorithm and abortAlgorithm algorithms must return
-promises; if supplied, sizeAlgorithm must be an algorithm accepting chunk objects and returning a
-number; and if supplied, highWaterMark must be a non-negative, non-NaN number.
-
-CreateWritableStream throws an exception if and only if the supplied startAlgorithm
-throws.
-
-
- 1. If _highWaterMark_ was not passed, set it to *1*.
- 1. If _sizeAlgorithm_ was not passed, set it to an algorithm that returns *1*.
- 1. Assert: ! IsNonNegativeNumber(_highWaterMark_) is *true*.
- 1. Let _stream_ be ObjectCreate(the original value of `WritableStream`'s `prototype` property).
- 1. Perform ! InitializeWritableStream(_stream_).
- 1. Let _controller_ be ObjectCreate(the original value of `WritableStreamDefaultController`'s `prototype`
- property).
- 1. Perform ? SetUpWritableStreamDefaultController(_stream_, _controller_, _startAlgorithm_, _writeAlgorithm_,
- _closeAlgorithm_, _abortAlgorithm_, _highWaterMark_, _sizeAlgorithm_).
- 1. Return _stream_.
-
-
-InitializeWritableStream ( stream
-)
-
-
- 1. Set _stream_.[[state]] to `"writable"`.
- 1. Set _stream_.[[storedError]], _stream_.[[writer]], _stream_.[[writableStreamController]],
- _stream_.[[inFlightWriteRequest]], _stream_.[[closeRequest]], _stream_.[[inFlightCloseRequest]] and
- _stream_.[[pendingAbortRequest]] to *undefined*.
- 1. Set _stream_.[[writeRequests]] to a new empty List.
- 1. Set _stream_.[[backpressure]] to *false*.
-
-
-IsWritableStream ( x )
-
-
- 1. If Type(_x_) is not Object, return *false*.
- 1. If _x_ does not have a [[writableStreamController]] internal slot, return *false*.
- 1. Return *true*.
-
-
-IsWritableStreamLocked ( stream
-)
-
-This abstract operation is meant to be called from other specifications that may wish to query whether or not a
-writable stream is locked to a writer.
-
-
- 1. Assert: ! IsWritableStream(_stream_) is *true*.
- 1. If _stream_.[[writer]] is *undefined*, return *false*.
- 1. Return *true*.
-
-
-WritableStreamAbort ( stream,
-reason )
-
-
- 1. Let _state_ be _stream_.[[state]].
- 1. If _state_ is `"closed"` or `"errored"`, return a promise resolved with *undefined*.
- 1. If _stream_.[[pendingAbortRequest]] is not *undefined*, return _stream_.[[pendingAbortRequest]].[[promise]].
- 1. Assert: _state_ is `"writable"` or `"erroring"`.
- 1. Let _wasAlreadyErroring_ be *false*.
- 1. If _state_ is `"erroring"`,
- 1. Set _wasAlreadyErroring_ to *true*.
- 1. Set _reason_ to *undefined*.
- 1. Let _promise_ be a new promise.
- 1. Set _stream_.[[pendingAbortRequest]] to Record {[[promise]]: _promise_, [[reason]]: _reason_,
- [[wasAlreadyErroring]]: _wasAlreadyErroring_}.
- 1. If _wasAlreadyErroring_ is *false*, perform ! WritableStreamStartErroring(_stream_, _reason_).
- 1. Return _promise_.
-
-
-WritableStreamClose ( stream )
-
-
- 1. Let _state_ be _stream_.[[state]].
- 1. If _state_ is `"closed"` or `"errored"`, return a promise rejected with a *TypeError* exception.
- 1. Assert: _state_ is `"writable"` or `"erroring"`.
- 1. Assert: ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*.
- 1. Let _promise_ be a new promise.
- 1. Set _stream_.[[closeRequest]] to _promise_.
- 1. Let _writer_ be _stream_.[[writer]].
- 1. If _writer_ is not *undefined*, and _stream_.[[backpressure]] is *true*, and _state_ is `"writable"`,
- resolve _writer_.[[readyPromise]] with *undefined*.
- 1. Perform ! WritableStreamDefaultControllerClose(_stream_.[[writableStreamController]]).
- 1. Return _promise_.
-
-
-Writable stream abstract operations used by controllers
-
-To allow future flexibility to add different writable stream behaviors (similar to the distinction between default
-readable streams and readable byte streams), much of the internal state of a writable stream is
-encapsulated by the {{WritableStreamDefaultController}} class.
-
-The abstract operations in this section are interfaces that are used by the controller implementation to affect its
-associated {{WritableStream}} object, translating the controller's internal state changes into developer-facing results
-visible through the {{WritableStream}}'s public API.
-
-WritableStreamAddWriteRequest (
-stream )
-
-
- 1. Assert: ! IsWritableStreamLocked(_stream_) is *true*.
- 1. Assert: _stream_.[[state]] is `"writable"`.
- 1. Let _promise_ be a new promise.
- 1. Append _promise_ as the last element of _stream_.[[writeRequests]].
- 1. Return _promise_.
-
-
-WritableStreamDealWithRejection ( stream, error )
-
-
- 1. Let _state_ be _stream_.[[state]].
- 1. If _state_ is `"writable"`,
- 1. Perform ! WritableStreamStartErroring(_stream_, _error_).
- 1. Return.
- 1. Assert: _state_ is `"erroring"`.
- 1. Perform ! WritableStreamFinishErroring(_stream_).
-
-
-WritableStreamStartErroring (
-stream, reason )
-
-
- 1. Assert: _stream_.[[storedError]] is *undefined*.
- 1. Assert: _stream_.[[state]] is `"writable"`.
- 1. Let _controller_ be _stream_.[[writableStreamController]].
- 1. Assert: _controller_ is not *undefined*.
- 1. Set _stream_.[[state]] to `"erroring"`.
- 1. Set _stream_.[[storedError]] to _reason_.
- 1. Let _writer_ be _stream_.[[writer]].
- 1. If _writer_ is not *undefined*, perform !
- WritableStreamDefaultWriterEnsureReadyPromiseRejected(_writer_, _reason_).
- 1. If ! WritableStreamHasOperationMarkedInFlight(_stream_) is *false* and _controller_.[[started]] is *true*, perform
- ! WritableStreamFinishErroring(_stream_).
-
-
-WritableStreamFinishErroring
-( stream )
-
-
- 1. Assert: _stream_.[[state]] is `"erroring"`.
- 1. Assert: ! WritableStreamHasOperationMarkedInFlight(_stream_) is *false*.
- 1. Set _stream_.[[state]] to `"errored"`.
- 1. Perform ! _stream_.[[writableStreamController]].[[ErrorSteps]]().
- 1. Let _storedError_ be _stream_.[[storedError]].
- 1. Repeat for each _writeRequest_ that is an element of _stream_.[[writeRequests]],
- 1. Reject _writeRequest_ with _storedError_.
- 1. Set _stream_.[[writeRequests]] to an empty List.
- 1. If _stream_.[[pendingAbortRequest]] is *undefined*,
- 1. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(_stream_).
- 1. Return.
- 1. Let _abortRequest_ be _stream_.[[pendingAbortRequest]].
- 1. Set _stream_.[[pendingAbortRequest]] to *undefined*.
- 1. If _abortRequest_.[[wasAlreadyErroring]] is *true*,
- 1. Reject _abortRequest_.[[promise]] with _storedError_.
- 1. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(_stream_).
- 1. Return.
- 1. Let _promise_ be ! stream.[[writableStreamController]].[[AbortSteps]](_abortRequest_.[[reason]]).
- 1. Upon fulfillment of _promise_,
- 1. Resolve _abortRequest_.[[promise]] with *undefined*.
- 1. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(_stream_).
- 1. Upon rejection of _promise_ with reason _reason_,
- 1. Reject _abortRequest_.[[promise]] with _reason_.
- 1. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(_stream_).
-
-
-WritableStreamFinishInFlightWrite ( stream )
-
-
- 1. Assert: _stream_.[[inFlightWriteRequest]] is not *undefined*.
- 1. Resolve _stream_.[[inFlightWriteRequest]] with *undefined*.
- 1. Set _stream_.[[inFlightWriteRequest]] to *undefined*.
-
-
-WritableStreamFinishInFlightWriteWithError ( stream, error )
-
-
- 1. Assert: _stream_.[[inFlightWriteRequest]] is not *undefined*.
- 1. Reject _stream_.[[inFlightWriteRequest]] with _error_.
- 1. Set _stream_.[[inFlightWriteRequest]] to *undefined*.
- 1. Assert: _stream_.[[state]] is `"writable"` or `"erroring"`.
- 1. Perform ! WritableStreamDealWithRejection(_stream_, _error_).
-
-
-WritableStreamFinishInFlightClose ( stream )
-
-
- 1. Assert: _stream_.[[inFlightCloseRequest]] is not *undefined*.
- 1. Resolve _stream_.[[inFlightCloseRequest]] with *undefined*.
- 1. Set _stream_.[[inFlightCloseRequest]] to *undefined*.
- 1. Let _state_ be _stream_.[[state]].
- 1. Assert: _stream_.[[state]] is `"writable"` or `"erroring"`.
- 1. If _state_ is `"erroring"`,
- 1. Set _stream_.[[storedError]] to *undefined*.
- 1. If _stream_.[[pendingAbortRequest]] is not *undefined*,
- 1. Resolve _stream_.[[pendingAbortRequest]].[[promise]] with *undefined*.
- 1. Set _stream_.[[pendingAbortRequest]] to *undefined*.
- 1. Set _stream_.[[state]] to `"closed"`.
- 1. Let _writer_ be _stream_.[[writer]].
- 1. If _writer_ is not *undefined*, resolve _writer_.[[closedPromise]] with *undefined*.
- 1. Assert: _stream_.[[pendingAbortRequest]] is *undefined*.
- 1. Assert: _stream_.[[storedError]] is *undefined*.
-
-
-WritableStreamFinishInFlightCloseWithError ( stream, error )
-
-
- 1. Assert: _stream_.[[inFlightCloseRequest]] is not *undefined*.
- 1. Reject _stream_.[[inFlightCloseRequest]] with _error_.
- 1. Set _stream_.[[inFlightCloseRequest]] to *undefined*.
- 1. Assert: _stream_.[[state]] is `"writable"` or `"erroring"`.
- 1. If _stream_.[[pendingAbortRequest]] is not *undefined*,
- 1. Reject _stream_.[[pendingAbortRequest]].[[promise]] with _error_.
- 1. Set _stream_.[[pendingAbortRequest]] to *undefined*.
- 1. Perform ! WritableStreamDealWithRejection(_stream_, _error_).
-
-
-
-WritableStreamCloseQueuedOrInFlight ( stream )
-
-
- 1. If _stream_.[[closeRequest]] is *undefined* and _stream_.[[inFlightCloseRequest]] is *undefined*, return *false*.
- 1. Return *true*.
-
-
-WritableStreamHasOperationMarkedInFlight ( stream )
-
-
- 1. If _stream_.[[inFlightWriteRequest]] is *undefined* and _controller_.[[inFlightCloseRequest]] is *undefined*, return *false*.
- 1. Return *true*.
-
-
-WritableStreamMarkCloseRequestInFlight ( stream )
-
-
- 1. Assert: _stream_.[[inFlightCloseRequest]] is *undefined*.
- 1. Assert: _stream_.[[closeRequest]] is not *undefined*.
- 1. Set _stream_.[[inFlightCloseRequest]] to _stream_.[[closeRequest]].
- 1. Set _stream_.[[closeRequest]] to *undefined*.
-
-
-WritableStreamMarkFirstWriteRequestInFlight ( stream )
-
-
- 1. Assert: _stream_.[[inFlightWriteRequest]] is *undefined*.
- 1. Assert: _stream_.[[writeRequests]] is not empty.
- 1. Let _writeRequest_ be the first element of _stream_.[[writeRequests]].
- 1. Remove _writeRequest_ from _stream_.[[writeRequests]], shifting all other elements downward (so that the second
- becomes the first, and so on).
- 1. Set _stream_.[[inFlightWriteRequest]] to _writeRequest_.
-
-
-WritableStreamRejectCloseAndClosedPromiseIfNeeded (
-stream )
-
-
- 1. Assert: _stream_.[[state]] is `"errored"`.
- 1. If _stream_.[[closeRequest]] is not *undefined*,
- 1. Assert: _stream_.[[inFlightCloseRequest]] is *undefined*.
- 1. Reject _stream_.[[closeRequest]] with _stream_.[[storedError]].
- 1. Set _stream_.[[closeRequest]] to *undefined*.
- 1. Let _writer_ be _stream_.[[writer]].
- 1. If _writer_ is not *undefined*,
- 1. Reject _writer_.[[closedPromise]] with _stream_.[[storedError]].
- 1. Set _writer_.[[closedPromise]].[[PromiseIsHandled]] to *true*.
-
-
-WritableStreamUpdateBackpressure ( stream, backpressure )
-
-
- 1. Assert: _stream_.[[state]] is `"writable"`.
- 1. Assert: ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*.
- 1. Let _writer_ be _stream_.[[writer]].
- 1. If _writer_ is not *undefined* and _backpressure_ is not _stream_.[[backpressure]],
- 1. If _backpressure_ is *true*, set _writer_.[[readyPromise]] to a new promise.
- 1. Otherwise,
- 1. Assert: _backpressure_ is *false*.
- 1. Resolve _writer_.[[readyPromise]] with *undefined*.
- 1. Set _stream_.[[backpressure]] to _backpressure_.
-
-
-Class
-WritableStreamDefaultWriter
-
-The {{WritableStreamDefaultWriter}} class represents a writable stream writer designed to be vended by a
-{{WritableStream}} instance.
-
-Class definition
+
+ ReadableByteStreamControllerClose(|controller|)
+ performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. If |controller|.\[[closeRequested]] is true or |stream|.\[[state]] is not "`readable`", return.
+ 1. If |controller|.\[[queueTotalSize]] > 0,
+ 1. Set |controller|.\[[closeRequested]] to true.
+ 1. Return.
+ 1. If |controller|.\[[pendingPullIntos]] is not empty,
+ 1. Let |firstPendingPullInto| be |controller|.\[[pendingPullIntos]][0].
+ 1. If |firstPendingPullInto|.\[[bytesFilled]] > 0,
+ 1. Let |e| be a new {{TypeError}} exception.
+ 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |e|).
+ 1. Throw |e|.
+ 1. Perform ! [$ReadableByteStreamControllerClearAlgorithms$](|controller|).
+ 1. Perform ! [$ReadableStreamClose$](|stream|).
+
-
+
+ ReadableByteStreamControllerCommitPullIntoDescriptor(|stream|,
+ |pullIntoDescriptor|) performs the following steps:
+
+ 1. Assert: |stream|.\[[state]] is not "`errored`".
+ 1. Let |done| be false.
+ 1. If |stream|.\[[state]] is "`closed`",
+ 1. Assert: |pullIntoDescriptor|.\[[bytesFilled]] is 0.
+ 1. Set |done| to true.
+ 1. Let |filledView| be !
+ [$ReadableByteStreamControllerConvertPullIntoDescriptor$](|pullIntoDescriptor|).
+ 1. If |pullIntoDescriptor|.\[[readerType]] is "`default`",
+ 1. Perform ! [$ReadableStreamFulfillReadRequest$](|stream|, |filledView|, |done|).
+ 1. Otherwise,
+ 1. Assert: |pullIntoDescriptor|.\[[readerType]] is "`byob`".
+ 1. Perform ! [$ReadableStreamFulfillReadIntoRequest$](|stream|, |filledView|, |done|).
+
-This section is non-normative.
+
+ ReadableByteStreamControllerConvertPullIntoDescriptor(|pullIntoDescriptor|)
+ performs the following steps:
+
+ 1. Let |bytesFilled| be |pullIntoDescriptor|.\[[bytesFilled]].
+ 1. Let |elementSize| be |pullIntoDescriptor|.\[[elementSize]].
+ 1. Assert: |bytesFilled| ≤ |pullIntoDescriptor|.\[[byteLength]].
+ 1. Assert: |bytesFilled| mod |elementSize| is 0.
+ 1. Return ! [$Construct$](|pullIntoDescriptor|.\[[ctor]], « |pullIntoDescriptor|.\[[buffer]],
+ |pullIntoDescriptor|.\[[byteOffset]], |bytesFilled| ÷ |elementSize| »).
+
-If one were to write the {{WritableStreamDefaultWriter}} class in something close to the syntax of [[!ECMASCRIPT]], it
-would look like
+
+ ReadableByteStreamControllerEnqueue(|controller|,
+ |chunk|) performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. If |controller|.\[[closeRequested]] is true or |stream|.\[[state]] is not "`readable`", return.
+ 1. Let |buffer| be |chunk|.\[[ViewedArrayBuffer]].
+ 1. Let |byteOffset| be |chunk|.\[[ByteOffset]].
+ 1. Let |byteLength| be |chunk|.\[[ByteLength]].
+ 1. Let |transferredBuffer| be ! [$TransferArrayBuffer$](|buffer|).
+ 1. If ! [$ReadableStreamHasDefaultReader$](|stream|) is true
+ 1. If ! [$ReadableStreamGetNumReadRequests$](|stream|) is 0,
+ 1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|,
+ |transferredBuffer|, |byteOffset|, |byteLength|).
+ 1. Otherwise,
+ 1. Assert: |controller|.\[[queue]] [=list/is empty=].
+ 1. Let |transferredView| be ! [$Construct$]({{%Uint8Array%}}, « |transferredBuffer|,
+ |byteOffset|, |byteLength| »).
+ 1. Perform ! [$ReadableStreamFulfillReadRequest$](|stream|, |transferredView|, false).
+ 1. Otherwise, if ! [$ReadableStreamHasBYOBReader$](|stream|) is true,
+ 1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|,
+ |transferredBuffer|, |byteOffset|, |byteLength|).
+ 1. Perform ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
+ 1. Otherwise,
+ 1. Assert: ! [$IsReadableStreamLocked$](|stream|) is false.
+ 1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|,
+ |transferredBuffer|, |byteOffset|, |byteLength|).
+ 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|).
+
-
- class WritableStreamDefaultWriter {
- constructor(stream)
+
+ ReadableByteStreamControllerEnqueueChunkToQueue(|controller|,
+ |buffer|, |byteOffset|, |byteLength|) performs the following steps:
- get closed()
- get desiredSize()
- get ready()
+ 1. [=list/Append=] Record {\[[buffer]]: |buffer|, \[[byteOffset]]: |byteOffset|, \[[byteLength]]:
+ |byteLength|} to |controller|.\[[queue]].
+ 1. Set |controller|.\[[queueTotalSize]] to |controller|.\[[queueTotalSize]] + |byteLength|.
+
- abort(reason)
- close()
- releaseLock()
- write(chunk)
- }
-
+
+ ReadableByteStreamControllerError(|controller|,
+ |e|) performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. If |stream|.\[[state]] is not "`readable`", return.
+ 1. Perform ! [$ReadableByteStreamControllerClearPendingPullIntos$](|controller|).
+ 1. Perform ! [$ResetQueue$](|controller|).
+ 1. Perform ! [$ReadableByteStreamControllerClearAlgorithms$](|controller|).
+ 1. Perform ! [$ReadableStreamError$](|stream|, |e|).
+
+
+
+ ReadableByteStreamControllerFillHeadPullIntoDescriptor(|controller|,
+ |size|, |pullIntoDescriptor|) performs the following steps:
+ 1. Assert: either |controller|.\[[pendingPullIntos]] [=list/is empty=], or
+ |controller|.\[[pendingPullIntos]][0] is |pullIntoDescriptor|.
+ 1. Perform ! [$ReadableByteStreamControllerInvalidateBYOBRequest$](|controller|).
+ 1. Set |pullIntoDescriptor|.\[[bytesFilled]] to |pullIntoDescriptor|.\[[bytesFilled]] + |size|.
-Internal slots
+
+ ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(|controller|,
+ |pullIntoDescriptor|) performs the following steps:
+
+ 1. Let |elementSize| be |pullIntoDescriptor|.\[[elementSize]].
+ 1. Let |currentAlignedBytes| be |pullIntoDescriptor|.\[[bytesFilled]] −
+ (|pullIntoDescriptor|.\[[bytesFilled]] mod |elementSize|).
+ 1. Let |maxBytesToCopy| be min(|controller|.\[[queueTotalSize]],
+ |pullIntoDescriptor|.\[[byteLength]] − |pullIntoDescriptor|.\[[bytesFilled]]).
+ 1. Let |maxBytesFilled| be |pullIntoDescriptor|.\[[bytesFilled]] + |maxBytesToCopy|.
+ 1. Let |maxAlignedBytes| be |maxBytesFilled| − (|maxBytesFilled| mod |elementSize|).
+ 1. Let |totalBytesToCopyRemaining| be |maxBytesToCopy|.
+ 1. Let |ready| be false.
+ 1. If |maxAlignedBytes| > |currentAlignedBytes|,
+ 1. Set |totalBytesToCopyRemaining| to |maxAlignedBytes| − |pullIntoDescriptor|.\[[bytesFilled]].
+ 1. Set |ready| to true.
+ 1. Let |queue| be |controller|.\[[queue]].
+ 1. [=While=] |totalBytesToCopyRemaining| > 0,
+ 1. Let |headOfQueue| be |queue|[0].
+ 1. Let |bytesToCopy| be min(|totalBytesToCopyRemaining|, |headOfQueue|.\[[byteLength]]).
+ 1. Let |destStart| be |pullIntoDescriptor|.\[[byteOffset]] +
+ |pullIntoDescriptor|.\[[bytesFilled]].
+ 1. Perform ! [$CopyDataBlockBytes$](|pullIntoDescriptor|.\[[buffer]].\[[ArrayBufferData]],
+ |destStart|, |headOfQueue|.\[[buffer]].\[[ArrayBufferData]], |headOfQueue|.\[[byteOffset]],
+ |bytesToCopy|).
+ 1. If |headOfQueue|.\[[byteLength]] is |bytesToCopy|,
+ 1. [=list/Remove=] |queue|[0].
+ 1. Otherwise,
+ 1. Set |headOfQueue|.\[[byteOffset]] to |headOfQueue|.\[[byteOffset]] + |bytesToCopy|.
+ 1. Set |headOfQueue|.\[[byteLength]] to |headOfQueue|.\[[byteLength]] − |bytesToCopy|.
+ 1. Set |controller|.\[[queueTotalSize]] to |controller|.\[[queueTotalSize]] − |bytesToCopy|.
+ 1. Perform ! [$ReadableByteStreamControllerFillHeadPullIntoDescriptor$](|controller|,
+ |bytesToCopy|, |pullIntoDescriptor|).
+ 1. Set |totalBytesToCopyRemaining| to |totalBytesToCopyRemaining| − |bytesToCopy|.
+ 1. If |ready| is false,
+ 1. Assert: |controller|.\[[queueTotalSize]] is 0.
+ 1. Assert: |pullIntoDescriptor|.\[[bytesFilled]] > 0.
+ 1. Assert: |pullIntoDescriptor|.\[[bytesFilled]] < |pullIntoDescriptor|.\[[elementSize]].
+ 1. Return |ready|.
+
+
+
+ ReadableByteStreamControllerGetDesiredSize(|controller|)
+ performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. Let |state| be |stream|.\[[state]].
+ 1. If |state| is "`errored`", return null.
+ 1. If |state| is "`closed`", return 0.
+ 1. Return |controller|.\[[strategyHWM]] − |controller|.\[[queueTotalSize]].
+
+
+
+ ReadableByteStreamControllerHandleQueueDrain(|controller|)
+ performs the following steps:
+
+ 1. Assert: |controller|.\[[controlledReadableStream]].\[[state]] is "`readable`".
+ 1. If |controller|.\[[queueTotalSize]] is 0 and |controller|.\[[closeRequested]] is true,
+ 1. Perform ! [$ReadableByteStreamControllerClearAlgorithms$](|controller|).
+ 1. Perform ! [$ReadableStreamClose$](|controller|.\[[controlledReadableStream]]).
+ 1. Otherwise,
+ 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|).
+
+
+
+ ReadableByteStreamControllerInvalidateBYOBRequest(|controller|)
+ performs the following steps:
+
+ 1. If |controller|.\[[byobRequest]] is undefined, return.
+ 1. Set |controller|.\[[byobRequest]].\[[controller]] to undefined.
+ 1. Set |controller|.\[[byobRequest]].\[[view]] to undefined.
+ 1. Set |controller|.\[[byobRequest]] to undefined.
+
+
+
+ ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(|controller|)
+ performs the following steps:
+
+ 1. Assert: |controller|.\[[closeRequested]] is false.
+ 1. [=While=] |controller|.\[[pendingPullIntos]] is not [=list/is empty|empty=],
+ 1. If |controller|.\[[queueTotalSize]] is 0, return.
+ 1. Let |pullIntoDescriptor| be |controller|.\[[pendingPullIntos]][0].
+ 1. If ! [$ReadableByteStreamControllerFillPullIntoDescriptorFromQueue$](|controller|,
+ |pullIntoDescriptor|) is true,
+ 1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|).
+ 1. Perform !
+ [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.\[[controlledReadableStream]],
+ |pullIntoDescriptor|).
+
+
+
+ ReadableByteStreamControllerPullInto(|controller|,
+ |view|) performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. Let |elementSize| be 1.
+ 1. Let |ctor| be {{%DataView%}}.
+ 1. If |view| has a \[[TypedArrayName]] internal slot (i.e., it is not a {{DataView}}),
+ 1. Set |elementSize| to the element size specified in [=the typed array constructors table=] for
+ |view|.\[[TypedArrayName]].
+ 1. Set |ctor| to the constructor specified in [=the typed array constructors table=] for
+ |view|.\[[TypedArrayName]].
+ 1. Let |byteOffset| be |view|.\[[ByteOffset]].
+ 1. Let |byteLength| be |view|.\[[ByteLength]].
+ 1. Let |buffer| be ! [$TransferArrayBuffer$](|view|.\[[ViewedArrayBuffer]]).
+ 1. Let |pullIntoDescriptor| be Record {\[[buffer]]: |buffer|, \[[byteOffset]]: |byteOffset|,
+ \[[byteLength]]: |byteLength|, \[[bytesFilled]]: 0, \[[elementSize]]: |elementSize|,
+ \[[ctor]]: |ctor|, \[[readerType]]: "`byob`"}.
+ 1. If |controller|.\[[pendingPullIntos]] is not empty,
+ 1. [=list/Append=] |pullIntoDescriptor| to |controller|.\[[pendingPullIntos]].
+ 1. Return ! [$ReadableStreamAddReadIntoRequest$](|stream|).
+ 1. If |stream|.\[[state]] is "`closed`",
+ 1. Let |emptyView| be ! [$Construct$](|ctor|, « |pullIntoDescriptor|.\[[buffer]],
+ |pullIntoDescriptor|.\[[byteOffset]], 0 »).
+ 1. Return [=a promise resolved with=] ! [$ReadableStreamCreateReadResult$](|emptyView|, true,
+ |stream|.\[[reader]].\[[forAuthorCode]]).
+ 1. If |controller|.\[[queueTotalSize]] > 0,
+ 1. If ! [$ReadableByteStreamControllerFillPullIntoDescriptorFromQueue$](|controller|,
+ |pullIntoDescriptor|) is true,
+ 1. Let |filledView| be !
+ [$ReadableByteStreamControllerConvertPullIntoDescriptor$](|pullIntoDescriptor|).
+ 1. Perform ! [$ReadableByteStreamControllerHandleQueueDrain$](|controller|).
+ 1. Return [=a promise resolved with=] ! [$ReadableStreamCreateReadResult$](|filledView|, false,
+ |stream|.\[[reader]].\[[forAuthorCode]]).
+ 1. If |controller|.\[[closeRequested]] is true,
+ 1. Let |e| be a {{TypeError}} exception.
+ 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |e|).
+ 1. Return [=a promise rejected with=] |e|.
+ 1. [=list/Append=] |pullIntoDescriptor| to |controller|.\[[pendingPullIntos]].
+ 1. Let |promise| be ! [$ReadableStreamAddReadIntoRequest$](|stream|).
+ 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|).
+ 1. Return |promise|.
+
+
+
+ ReadableByteStreamControllerRespond(|controller|,
+ |bytesWritten|) performs the following steps:
+
+ 1. Assert: |controller|.\[[pendingPullIntos]] is not empty.
+ 1. Perform ? [$ReadableByteStreamControllerRespondInternal$](|controller|, |bytesWritten|).
+
+
+
+ ReadableByteStreamControllerRespondInClosedState(|controller|,
+ |firstDescriptor|) performs the following steps:
+
+ 1. Set |firstDescriptor|.\[[buffer]] to ! [$TransferArrayBuffer$](|firstDescriptor|.\[[buffer]]).
+ 1. Assert: |firstDescriptor|.\[[bytesFilled]] is 0.
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. If ! [$ReadableStreamHasBYOBReader$](|stream|) is true,
+ 1. [=While=] ! [$ReadableStreamGetNumReadIntoRequests$](|stream|) > 0,
+ 1. Let |pullIntoDescriptor| be !
+ [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|).
+ 1. Perform ! [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|stream|,
+ |pullIntoDescriptor|).
+
+
+
+ ReadableByteStreamControllerRespondInReadableState(|controller|,
+ |bytesWritten|, |pullIntoDescriptor|) performs the following steps:
+
+ 1. If |pullIntoDescriptor|.\[[bytesFilled]] + |bytesWritten| >
+ |pullIntoDescriptor|.\[[byteLength]], throw a {{RangeError}} exception.
+ 1. Perform ! [$ReadableByteStreamControllerFillHeadPullIntoDescriptor$](|controller|,
+ |bytesWritten|, |pullIntoDescriptor|).
+ 1. If |pullIntoDescriptor|.\[[bytesFilled]] < |pullIntoDescriptor|.\[[elementSize]], return.
+ 1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|).
+ 1. Let |remainderSize| be |pullIntoDescriptor|.\[[bytesFilled]] mod
+ |pullIntoDescriptor|.\[[elementSize]].
+ 1. If |remainderSize| > 0,
+ 1. Let |end| be |pullIntoDescriptor|.\[[byteOffset]] + |pullIntoDescriptor|.\[[bytesFilled]].
+ 1. Let |remainder| be ? [$CloneArrayBuffer$](|pullIntoDescriptor|.\[[buffer]], |end| −
+ |remainderSize|, |remainderSize|, {{%ArrayBuffer%}}).
+ 1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|, |remainder|, 0,
+ |remainder|.\[[ByteLength]]).
+ 1. Set |pullIntoDescriptor|.\[[buffer]] to ! [$TransferArrayBuffer$](|pullIntoDescriptor|.\[[buffer]]).
+ 1. Set |pullIntoDescriptor|.\[[bytesFilled]] to |pullIntoDescriptor|.\[[bytesFilled]] − |remainderSize|.
+ 1. Perform !
+ [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.\[[controlledReadableStream]],
+ |pullIntoDescriptor|).
+ 1. Perform ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
+
+
+
+ ReadableByteStreamControllerRespondInternal(|controller|,
+ |bytesWritten|) performs the following steps:
+
+ 1. Let |firstDescriptor| be |controller|.\[[pendingPullIntos]][0].
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. If |stream|.\[[state]] is "`closed`",
+ 1. If |bytesWritten| is not 0, throw a {{TypeError}} exception.
+ 1. Perform ! [$ReadableByteStreamControllerRespondInClosedState$](|controller|,
+ |firstDescriptor|).
+ 1. Otherwise,
+ 1. Assert: |stream|.\[[state]] is "`readable`".
+ 1. Perform ? [$ReadableByteStreamControllerRespondInReadableState$](|controller|, |bytesWritten|,
+ |firstDescriptor|).
+ 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|).
+
-Instances of {{WritableStreamDefaultWriter}} are created with the internal slots described in the following table:
+
+ ReadableByteStreamControllerRespondWithNewView(|controller|,
+ |view|) performs the following steps:
+
+ 1. Assert: |controller|.\[[pendingPullIntos]] is not [=list/is empty|empty=].
+ 1. Let |firstDescriptor| be |controller|.\[[pendingPullIntos]][0].
+ 1. If |firstDescriptor|.\[[byteOffset]] + |firstDescriptor|.\[[bytesFilled]] is not
+ |view|.\[[ByteOffset]], throw a {{RangeError}} exception.
+ 1. If |firstDescriptor|.\[[byteLength]] is not |view|.\[[ByteLength]], throw a {{RangeError}}
+ exception.
+ 1. Set |firstDescriptor|.\[[buffer]] to |view|.\[[ViewedArrayBuffer]].
+ 1. Perform ? [$ReadableByteStreamControllerRespondInternal$](|controller|, |view|.\[[ByteLength]]).
+
+
+
+ ReadableByteStreamControllerShiftPendingPullInto(|controller|)
+ performs the following steps:
+
+ 1. Let |descriptor| be |controller|.\[[pendingPullIntos]][0].
+ 1. [=list/Remove=] |descriptor| from |controller|.\[[pendingPullIntos]].
+ 1. Perform ! [$ReadableByteStreamControllerInvalidateBYOBRequest$](|controller|).
+ 1. Return |descriptor|.
+
+
+
+ ReadableByteStreamControllerShouldCallPull(|controller|)
+ performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledReadableStream]].
+ 1. If |stream|.\[[state]] is not "`readable`", return false.
+ 1. If |controller|.\[[closeRequested]] is true, return false.
+ 1. If |controller|.\[[started]] is false, return false.
+ 1. If ! [$ReadableStreamHasDefaultReader$](|stream|) is true and !
+ [$ReadableStreamGetNumReadRequests$](|stream|) > 0, return true.
+ 1. If ! [$ReadableStreamHasBYOBReader$](|stream|) is true and !
+ [$ReadableStreamGetNumReadIntoRequests$](|stream|) > 0, return true.
+ 1. Let |desiredSize| be ! [$ReadableByteStreamControllerGetDesiredSize$](|controller|).
+ 1. Assert: |desiredSize| is not null.
+ 1. If |desiredSize| > 0, return true.
+ 1. Return false.
+
+
+
+ SetUpReadableByteStreamController(|stream|,
+ |controller|, |startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|,
+ |autoAllocateChunkSize|) performs the following steps:
+
+ 1. Assert: |stream|.\[[readableStreamController]] is undefined.
+ 1. If |autoAllocateChunkSize| is not undefined,
+ 1. Assert: ! IsInteger(|autoAllocateChunkSize|) is true.
+ 1. Assert: |autoAllocateChunkSize| is positive.
+ 1. Set |controller|.\[[controlledReadableStream]] to |stream|.
+ 1. Set |controller|.\[[pullAgain]] and |controller|.\[[pulling]] to false.
+ 1. Set |controller|.\[[byobRequest]] to undefined.
+ 1. Perform ! [$ResetQueue$](|controller|).
+ 1. Set |controller|.\[[closeRequested]] and |controller|.\[[started]] to false.
+ 1. Set |controller|.\[[strategyHWM]] to |highWaterMark|.
+ 1. Set |controller|.\[[pullAlgorithm]] to |pullAlgorithm|.
+ 1. Set |controller|.\[[cancelAlgorithm]] to |cancelAlgorithm|.
+ 1. Set |controller|.\[[autoAllocateChunkSize]] to |autoAllocateChunkSize|.
+ 1. Set |controller|.\[[pendingPullIntos]] to a new empty [=list=].
+ 1. Set |stream|.\[[readableStreamController]] to |controller|.
+ 1. Let |startResult| be the result of performing |startAlgorithm|.
+ 1. Let |startPromise| be [=a promise resolved with=] |startResult|.
+ 1. [=Upon fulfillment=] of |startPromise|,
+ 1. Set |controller|.\[[started]] to true.
+ 1. Assert: |controller|.\[[pulling]] is false.
+ 1. Assert: |controller|.\[[pullAgain]] is false.
+ 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|).
+ 1. [=Upon rejection=] of |startPromise| with reason |r|,
+ 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |r|).
+
+
+
+ SetUpReadableByteStreamControllerFromUnderlyingSource(|stream|,
+ |underlyingSource|, |underlyingSourceDict|, |highWaterMark|) performs the following steps:
+
+ 1. Let |controller| be a [=new=] {{ReadableByteStreamController}}.
+ 1. Let |startAlgorithm| be an algorithm that returns undefined.
+ 1. Let |pullAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined.
+ 1. Let |cancelAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined.
+ 1. If |underlyingSourceDict|["{{UnderlyingSource/start}}"] [=map/exists=], then set
+ |startAlgorithm| to an algorithm which returns the result of [=invoking=]
+ |underlyingSourceDict|["{{UnderlyingSource/start}}"] with argument list
+ « |controller| » and [=callback this value=] |underlyingSource|.
+ 1. If |underlyingSourceDict|["{{UnderlyingSource/pull}}"] [=map/exists=], then set
+ |pullAlgorithm| to an algorithm which returns the result of [=invoking=]
+ |underlyingSourceDict|["{{UnderlyingSource/pull}}"] with argument list
+ « |controller| » and [=callback this value=] |underlyingSource|.
+ 1. If |underlyingSourceDict|["{{UnderlyingSource/cancel}}"] [=map/exists=], then set
+ |cancelAlgorithm| to an algorithm which takes an argument |reason| and returns the result of
+ [=invoking=] |underlyingSourceDict|["{{UnderlyingSource/cancel}}"] with argument list
+ « |reason| » and [=callback this value=] |underlyingSource|.
+ 1. Let |autoAllocateChunkSize| be
+ |underlyingSourceDict|["{{UnderlyingSource/autoAllocateChunkSize}}"], if it [=map/exists=], or
+ undefined otherwise.
+ 1. Perform ? [$SetUpReadableByteStreamController$](|stream|, |controller|, |startAlgorithm|,
+ |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, |autoAllocateChunkSize|).
+
+Writable streams
+
+Using writable streams
+
+
+ The usual way to write to a writable stream is to simply [=piping|pipe=] a [=readable stream=] to
+ it. This ensures that [=backpressure=] is respected, so that if the writable stream's [=underlying
+ sink=] is not able to accept data as fast as the readable stream can produce it, the readable
+ stream is informed of this and has a chance to slow down its data production.
+
+
+ readableStream.pipeTo(writableStream)
+ .then(() => console.log("All data successfully written!"))
+ .catch(e => console.error("Something went wrong!", e));
+
+
+
+
+ You can also write directly to writable streams by acquiring a [=writer=] and using its
+ {{WritableStreamDefaultWriter/write()}} and {{WritableStreamDefaultWriter/close()}} methods. Since
+ writable streams queue any incoming writes, and take care internally to forward them to the
+ [=underlying sink=] in sequence, you can indiscriminately write to a writable stream without much
+ ceremony:
+
+
+ function writeArrayToStream(array, writableStream) {
+ const writer = writableStream.getWriter();
+ array.forEach(chunk => writer.write(chunk).catch(() => {}));
+
+ return writer.close();
+ }
+
+ writeArrayToStream([1, 2, 3, 4, 5], writableStream)
+ .then(() => console.log("All done!"))
+ .catch(e => console.error("Error with the stream: " + e));
+
+
+ Note how we use .catch(() => {})
to suppress any rejections from the
+ {{WritableStreamDefaultWriter/write()}} method; we'll be notified of any fatal errors via a
+ rejection of the {{WritableStreamDefaultWriter/close()}} method, and leaving them un-caught would
+ cause potential {{unhandledrejection}} events and console warnings.
+
+
+
+ In the previous example we only paid attention to the success or failure of the entire stream, by
+ looking at the promise returned by the writer's {{WritableStreamDefaultWriter/close()}} method.
+ That promise will reject if anything goes wrong with the stream—initializing it, writing to it, or
+ closing it. And it will fulfill once the stream is successfully closed. Often this is all you care
+ about.
+
+ However, if you care about the success of writing a specific [=chunk=], you can use the promise
+ returned by the writer's {{WritableStreamDefaultWriter/write()}} method:
+
+
+ writer.write("i am a chunk of data")
+ .then(() => console.log("chunk successfully written!"))
+ .catch(e => console.error(e));
+
+
+ What "success" means is up to a given stream instance (or more precisely, its [=underlying sink=])
+ to decide. For example, for a file stream it could simply mean that the OS has accepted the write,
+ and not necessarily that the chunk has been flushed to disk. Some streams might not be able to
+ give such a signal at all, in which case the returned promise will fulfill immediately.
+
+
+
+ The {{WritableStreamDefaultWriter/desiredSize}} and {{WritableStreamDefaultWriter/ready}}
+ properties of writable stream writers allow [=producers=] to more precisely respond to flow
+ control signals from the stream, to keep memory usage below the stream's specified [=high water
+ mark=]. The following example writes an infinite sequence of random bytes to a stream, using
+ {{WritableStreamDefaultWriter/desiredSize}} to determine how many bytes to generate at a given
+ time, and using {{WritableStreamDefaultWriter/ready}} to wait for the [=backpressure=] to subside.
+
+
+ async function writeRandomBytesForever(writableStream) {
+ const writer = writableStream.getWriter();
+
+ while (true) {
+ await writer.ready;
+
+ const bytes = new Uint8Array(writer.desiredSize);
+ crypto.getRandomValues(bytes);
+
+ // Purposefully don't await; awaiting writer.ready is enough.
+ writer.write(bytes).catch(() => {});
+ }
+ }
+
+ writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e));
+
+
+ Note how we don't await
the promise returned by
+ {{WritableStreamDefaultWriter/write()}}; this would be redundant with await
ing the
+ {{WritableStreamDefaultWriter/ready}} promise. Additionally, similar to a previous example, we use the .catch(() =>
+ {})
pattern on the promises returned by {{WritableStreamDefaultWriter/write()}}; in this
+ case we'll be notified about any failures
+ await
ing the {{WritableStreamDefaultWriter/ready}} promise.
+
+
+
+ To further emphasize how it's a bad idea to await
the promise returned by
+ {{WritableStreamDefaultWriter/write()}}, consider a modification of the above example, where we
+ continue to use the {{WritableStreamDefaultWriter}} interface directly, but we don't control how
+ many bytes we have to write at a given time. In that case, the [=backpressure=]-respecting code
+ looks the same:
+
+
+ async function writeSuppliedBytesForever(writableStream, getBytes) {
+ const writer = writableStream.getWriter();
+
+ while (true) {
+ await writer.ready;
+
+ const bytes = getBytes();
+ writer.write(bytes).catch(() => {});
+ }
+ }
+
+
+ Unlike the previous example, where—because we were always writing exactly
+ {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} bytes each time—the
+ {{WritableStreamDefaultWriter/write()}} and {{WritableStreamDefaultWriter/ready}} promises were
+ synchronized, in this case it's quite possible that the {{WritableStreamDefaultWriter/ready}}
+ promise fulfills before the one returned by {{WritableStreamDefaultWriter/write()}} does.
+ Remember, the {{WritableStreamDefaultWriter/ready}} promise fulfills when the [=desired size to
+ fill a stream's internal queue|desired size=] becomes positive, which might be before the write
+ succeeds (especially in cases with a larger [=high water mark=]).
+
+ In other words, await
ing the return value of {{WritableStreamDefaultWriter/write()}}
+ means you never queue up writes in the stream's [=internal queue=], instead only executing a write
+ after the previous one succeeds, which can result in low throughput.
+
+
+The {{WritableStream}} class
+
+The {{WritableStream}} represents a [=writable stream=].
+
+Interface definition
+
+The Web IDL definition for the {{WritableStream}} class is given as follows:
+
+
+[Exposed=(Window,Worker,Worklet)]
+interface WritableStream {
+ constructor(object underlyingSink, optional QueuingStrategy strategy = {});
+
+ readonly attribute boolean locked;
+
+ Promise abort(optional any reason);
+ Promise close();
+ WritableStreamDefaultWriter getWriter();
+};
+
+
+Internal slots
+
+Instances of {{WritableStream}} are created with the internal slots described in the following
+table:
-
-
- Internal Slot
- Description (non-normative)
-
-
-
- \[[closedPromise]]
- A promise returned by the writer's {{WritableStreamDefaultWriter/closed}} getter
-
+
- \[[ownerWritableStream]]
- A {{WritableStream}} instance that owns this writer
-
+ Internal Slot
+ Description (non-normative)
+
- \[[readyPromise]]
- A promise returned by the writer's {{WritableStreamDefaultWriter/ready}} getter
-
+ \[[backpressure]]
+ A boolean indicating the backpressure signal set by the controller
+
+ \[[closeRequest]]
+ The promise returned from the writer's
+ {{WritableStreamDefaultWriter/close()}} method
+
+ \[[inFlightWriteRequest]]
+ A slot set to the promise for the current in-flight write operation
+ while the [=underlying sink=]'s write algorithm is executing and has not yet fulfilled, used to
+ prevent reentrant calls
+
+ \[[inFlightCloseRequest]]
+ A slot set to the promise for the current in-flight close operation
+ while the [=underlying sink=]'s close algorithm is executing and has not yet fulfilled, used to
+ prevent the {{WritableStreamDefaultWriter/abort()}} method from interrupting close
+
+ \[[pendingAbortRequest]]
+ A Record containing the promise returned from
+ {{WritableStreamDefaultWriter/abort()}} and the reason passed to
+ {{WritableStreamDefaultWriter/abort()}}
+
+ \[[state]]
+ A string containing the stream's current state, used internally; one of
+ "`writable`", "`closed`", "`erroring`", or "`errored`"
+
+ \[[storedError]]
+ A value indicating how the stream failed, to be given as a failure
+ reason or exception when trying to operate on the stream while in the "`errored`" state
+
+ \[[writableStreamController]]
+ A {{WritableStreamDefaultController}} created with the ability to
+ control the state and queue of this stream
+
+ \[[writer]]
+ A {{WritableStreamDefaultWriter}} instance, if the stream is [=locked to
+ a writer=], or undefined if it is not
+
+ \[[writeRequests]]
+ A [=list=] of promises representing the stream's internal queue of write
+ requests not yet processed by the [=underlying sink=]
-new WritableStreamDefaultWriter(stream)
+The \[[inFlightCloseRequest]] slot and \[[closeRequest]] slot are mutually
+exclusive. Similarly, no element will be removed from \[[writeRequests]] while
+\[[inFlightWriteRequest]] is not undefined. Implementations can optimize storage for these slots
+based on these invariants.
-
- The WritableStreamDefaultWriter
constructor is generally not meant to be used directly; instead, a
- stream's {{WritableStream/getWriter()}} method ought to be used.
-
-
-
- 1. If ! IsWritableStream(_stream_) is *false*, throw a *TypeError* exception.
- 1. If ! IsWritableStreamLocked(_stream_) is *true*, throw a *TypeError* exception.
- 1. Set *this*.[[ownerWritableStream]] to _stream_.
- 1. Set _stream_.[[writer]] to *this*.
- 1. Let _state_ be _stream_.[[state]].
- 1. If _state_ is `"writable"`,
- 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false* and _stream_.[[backpressure]] is *true*,
- set *this*.[[readyPromise]] to a new promise.
- 1. Otherwise, set *this*.[[readyPromise]] to a promise resolved with *undefined*.
- 1. Set *this*.[[closedPromise]] to a new promise.
- 1. Otherwise, if _state_ is `"erroring"`,
- 1. Set *this*.[[readyPromise]] to a promise rejected with _stream_.[[storedError]].
- 1. Set *this*.[[readyPromise]].[[PromiseIsHandled]] to *true*.
- 1. Set *this*.[[closedPromise]] to a new promise.
- 1. Otherwise, if _state_ is `"closed"`,
- 1. Set *this*.[[readyPromise]] to a promise resolved with *undefined*.
- 1. Set *this*.[[closedPromise]] to a promise resolved with *undefined*.
- 1. Otherwise,
- 1. Assert: _state_ is `"errored"`.
- 1. Let _storedError_ be _stream_.[[storedError]].
- 1. Set *this*.[[readyPromise]] to a promise rejected with _storedError_.
- 1. Set *this*.[[readyPromise]].[[PromiseIsHandled]] to *true*.
- 1. Set *this*.[[closedPromise]] to a promise rejected with _storedError_.
- 1. Set *this*.[[closedPromise]].[[PromiseIsHandled]] to *true*.
-
+The underlying sink API
-Properties of the {{WritableStreamDefaultWriter}} prototype
+The {{WritableStream()}} constructor accepts as its first argument a JavaScript object representing
+the [=underlying sink=]. Such objects can contain any of the following properties:
-get closed
+
+dictionary UnderlyingSink {
+ WritableStreamStartCallback start;
+ WritableStreamWriteCallback write;
+ WritableStreamCloseCallback close;
+ WritableStreamAbortCallback abort;
+ any type;
+};
-
- The closed
getter returns a promise that will be fulfilled when the stream becomes closed, or rejected if
- the stream ever errors or the writer's lock is released before the stream finishes
- closing.
-
+callback WritableStreamStartCallback = Promise (WritableStreamDefaultController controller);
+callback WritableStreamWriteCallback = Promise (WritableStreamDefaultController controller, optional any chunk);
+callback WritableStreamCloseCallback = Promise ();
+callback WritableStreamAbortCallback = Promise (optional any reason);
+
-
- 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception.
- 1. Return *this*.[[closedPromise]].
-
+
+ - start(controller)
+ -
+
A function that is called immediately during creation of the {{WritableStream}}.
-
get desiredSize
+ Typically this is used to acquire access to the [=underlying sink=] resource being
+ represented.
-
- The desiredSize
getter returns the desired size to
- fill the stream's internal queue. It can be negative, if the queue is over-full. A producer can use this
- information to determine the right amount of data to write.
+ If this setup process is asynchronous, it can return a promise to signal success or failure; a
+ rejected promise will error the stream. Any thrown exceptions will be re-thrown by the
+ {{WritableStream()}} constructor.
- It will be null if the stream cannot be successfully written to (due to either being errored, or
- having an abort queued up). It will return zero if the stream is closed. The getter will throw an exception if invoked
- when the writer's lock is released.
-
+ - write(chunk,
+ controller)
+ -
+
A function that is called when a new [=chunk=] of data is ready to be written to the
+ [=underlying sink=]. The stream implementation guarantees that this function will be called only
+ after previous writes have succeeded, and never before {{UnderlyingSink/start|start()}} has
+ succeeded or after {{UnderlyingSink/close|close()}} or {{UnderlyingSink/abort|abort()}} have
+ been called.
+
+
This function is used to actually send the data to the resource presented by the [=underlying
+ sink=], for example by calling a lower-level API.
+
+
If the process of writing data is asynchronous, and communicates success or failure signals
+ back to its user, then this function can return a promise to signal success or failure. This
+ promise return value will be communicated back to the caller of
+ {{WritableStreamDefaultWriter/write()|writer.write()}}, so they can monitor that individual
+ write. Throwing an exception is treated the same as returning a rejected promise.
+
+
Note that such signals are not always available; compare e.g. [[#example-ws-no-backpressure]]
+ with [[#example-ws-backpressure]]. In such cases, it's best to not return anything.
+
+
The promise potentially returned by this function also governs whether the given chunk counts
+ as written for the purposes of computed the [=desired size to fill a stream's internal
+ queue|desired size to fill the stream's internal queue=]. That is, during the time it takes the
+ promise to settle, {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} will stay at
+ its previous value, only increasing to signal the desire for more chunks once the write
+ succeeds.
+
+
- close()
+ -
+
A function that is called after the [=producer=] signals, via
+ {{WritableStreamDefaultWriter/close()|writer.close()}}, that they are done writing [=chunks=] to
+ the stream, and subsequently all queued-up writes have successfully completed.
-
- 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, throw a *TypeError* exception.
- 1. If *this*.[[ownerWritableStream]] is *undefined*, throw a *TypeError* exception.
- 1. Return ! WritableStreamDefaultWriterGetDesiredSize(*this*).
-
+
This function can perform any actions necessary to finalize or flush writes to the
+ [=underlying sink=], and release access to any held resources.
-
get ready
+ If the shutdown process is asynchronous, the function can return a promise to signal success
+ or failure; the result will be communicated via the return value of the called
+ {{WritableStreamDefaultWriter/close()|writer.close()}} method. Additionally, a rejected promise
+ will error the stream, instead of letting it close successfully. Throwing an exception is
+ treated the same as returning a rejected promise.
-
- The ready
getter returns a promise that will be fulfilled when the desired size to fill the stream's internal queue transitions from non-positive to positive,
- signaling that it is no longer applying backpressure. Once the desired size to fill the stream's internal queue dips back to zero or below, the getter will return a new
- promise that stays pending until the next transition.
+ - close(reason)
+ -
+
A function that is called after the [=producer=] signals, via
+ {{WritableStream/abort()|stream.abort()}} or
+ {{WritableStreamDefaultWriter/abort()|writer.abort()}}, that they wish to [=abort a writable
+ stream|abort=] the stream. It takes as its argument the same value as was passed to those
+ methods by the producer.
+
+
Writable streams can additionally be aborted under certain conditions during [=piping=]; see
+ the definition of the {{ReadableStream/pipeTo()}} method for more details.
+
+
This function can clean up any held resources, much like {{UnderlyingSink/close|close()}},
+ but perhaps with some custom handling.
+
+
If the shutdown process is asynchronous, the function can return a promise to signal success
+ or failure; the result will be communicated via the return value of the called
+ {{WritableStreamDefaultWriter/abort()|writer.abort()}} method. Throwing an exception is treated
+ the same as returning a rejected promise. Regardless, the stream will be errored with a new
+ {{TypeError}} indicating that it was aborted.
+
+
- type
+ -
+
This property is reserved for future use, so any attempts to supply a value will throw an
+ exception.
+
- If the stream becomes errored or aborted, or the writer's lock is released, the
- returned promise will become rejected.
+The controller
argument passed to {{UnderlyingSink/start|start()}} and
+{{UnderlyingSink/write|write()}} is an instance of {{WritableStreamDefaultController}}, and has the
+ability to error the stream. This is mainly used for bridging the gap with non-promise-based APIs,
+as seen for example in [[#example-ws-no-backpressure]].
+
+
Constructor, methods, and properties
+
+
+ stream = new {{WritableStream/constructor(underlyingSink, strategy)|WritableStream}}(underlyingSink[, strategy)
+ -
+
Creates a new {{WritableStream}} wrapping the provided [=underlying sink=]. See
+ [[#underlying-sink-api]] for more details on the underlyingSink argument.
+
+
The |strategy| argument represents the stream's [=queuing strategy=], as described in
+ [[#qs-api]]. If it is not provided, the default behavior will be the same as a
+ {{CountQueuingStrategy}} with a [=high water mark=] of 1.
+
+
isLocked = stream.{{WritableStream/locked}}
+ -
+
Returns whether or not the writable stream is [=locked to a writer=].
+
+
await stream.{{WritableStream/abort(reason)|abort}}([ reason ])
+ -
+
[=abort a writable stream|Aborts=] the stream, signaling that the producer can no longer
+ successfully write to the stream and it is to be immediately moved to an errored state, with any
+ queued-up writes discarded. This will also execute any abort mechanism of the [=underlying
+ sink=].
+
+
The returned promise will fulfill if the stream shuts down successfully, or reject if the
+ underlying sink signaled that there was an error doing so. Additionally, it will reject with a
+ {{TypeError}} (without attempting to cancel the stream) if the stream is currently [=locked to a
+ writer|locked=].
+
+
await stream.{{WritableStream/close()|close}}()
+ -
+
Closes the stream. The [=underlying sink=] will finish processing any previously-written
+ [=chunks=], before invoking its close behavior. During this time any further attempts to write
+ will fail (without erroring the stream).
+
+
The method returns a promise that will fulfill if all remaining [=chunks=] are successfully
+ written and the stream successfully closes, or rejects if an error is encountered during this
+ process. Additionally, it will reject with a {{TypeError}} (without attempting to cancel the
+ stream) if the stream is currently [=locked to a writer|locked=].
+
+
writer = stream.{{WritableStream/getWriter()|getWriter}}()
+ -
+
Creates a [=writer=] (an instance of {{WritableStreamDefaultWriter}}) and [=locked to a
+ writer|locks=] the stream to the new writer. While the stream is locked, no other writer can be
+ acquired until this one is [=release a write lock|released=].
+
+
This functionality is especially useful for creating abstractions that desire the ability to
+ write to a stream without interruption or interleaving. By getting a writer for the stream, you
+ can ensure nobody else can write at the same time, which would cause the resulting written data
+ to be unpredictable and probably useless.
+
+
+
+ The WritableStream(|underlyingSink|,
+ |strategy|) constructor steps are:
+
+ 1. Let |underlyingSinkDict| be |underlyingSinkDict|, [=converted to an IDL value=] of type
+ {{UnderlyingSink}}.
+ We cannot declare the |underlyingSink| argument as having the {{UnderlyingSink}}
+ type directly, because doing so would lose the reference to the original object. We need to
+ retain the object so we can [=invoke=] the various methods on it.
+ 1. If |underlyingSinkDict|["{{UnderlyingSink/type}}"] [=map/exists=], throw a {{RangeError}}
+ exception.
+
This is to allow us to add new potential types in the future, without
+ backward-compatibility concerns.
+ 1. Perform ! [$InitializeWritableStream$]([=this=]).
+ 1. Let |sizeAlgorithm| be ? [$ExtractSizeAlgorithm$](|strategy|).
+ 1. Let |highWaterMark| be ? [$ExtractHighWaterMark$](|strategy|, 1).
+ 1. Perform ? [$SetUpWritableStreamDefaultControllerFromUnderlyingSink$]([=this=], |underlyingSink|,
+ |underlyingSinkDict|, |highWaterMark|, |sizeAlgorithm|).
-
- 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception.
- 1. Return *this*.[[readyPromise]].
-
+
+ The locked attribute's getter steps are:
-abort(reason)
+ 1. Return ! [$IsWritableStreamLocked$]([=this=]).
+
-
- If the writer is active, the abort
method behaves the same as that for the
- associated stream. (Otherwise, it returns a rejected promise.)
+
+ The abort(|reason|) method steps are:
+
+ 1. If ! [$IsWritableStreamLocked$]([=this=]) is true, return [=a promise rejected with=] a
+ {{TypeError}} exception.
+ 1. Return ! [$WritableStreamAbort$]([=this=], |reason|).
-
- 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception.
- 1. If *this*.[[ownerWritableStream]] is *undefined*, return a promise rejected with a *TypeError* exception.
- 1. Return ! WritableStreamDefaultWriterAbort(*this*, _reason_).
-
+
+ The close() method steps are:
-close()
+ 1. If ! [$IsWritableStreamLocked$]([=this=]) is true, return [=a promise rejected with=] a
+ {{TypeError}} exception.
+ 1. If ! [$WritableStreamCloseQueuedOrInFlight$]([=this=]) is true, return [=a promise rejected
+ with=] a {{TypeError}} exception.
+ 1. Return ! [$WritableStreamClose$]([=this=]).
+
-
- If the writer is active, the close
method behaves the same as that for the
- associated stream. (Otherwise, it returns a rejected promise.)
+
+ The getWriter() method steps are:
+
+ 1. Return ? [$AcquireWritableStreamDefaultWriter$]([=this=]).
-
- 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception.
- 1. Let _stream_ be *this*.[[ownerWritableStream]].
- 1. If _stream_ is *undefined*, return a promise rejected with a *TypeError* exception.
- 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *true*, return a promise rejected with a *TypeError*
- exception.
- 1. Return ! WritableStreamDefaultWriterClose(*this*).
-
+The {{WritableStreamDefaultWriter}} class
-releaseLock()
+The {{WritableStreamDefaultWriter}} class represents a [=writable stream writer=] designed to be
+vended by a {{WritableStream}} instance.
-
- The releaseLock
method releases the writer's lock on the corresponding
- stream. After the lock is released, the writer is no longer active. If the associated
- stream is errored when the lock is released, the writer will appear errored in the same way from now on; otherwise,
+Interface definition
+
+The Web IDL definition for the {{WritableStreamDefaultWriter}} class is given as follows:
+
+
+[Exposed=(Window,Worker,Worklet)]
+interface WritableStreamDefaultWriter {
+ constructor(WritableStream stream);
+
+ readonly attribute Promise closed;
+ readonly attribute unrestricted double? desiredSize;
+ readonly attribute Promise ready;
+
+ Promise abort(optional any reason);
+ Promise close();
+ void releaseLock();
+ Promise write(optional any chunk);
+};
+
+
+Internal slots
+
+Instances of {{WritableStreamDefaultWriter}} are created with the internal slots described in the
+following table:
+
+
+
+
+ Internal Slot
+ Description (non-normative)
+
+
+ \[[closedPromise]]
+ A promise returned by the writer's
+ {{WritableStreamDefaultWriter/closed}} getter
+
+ \[[ownerWritableStream]]
+ A {{WritableStream}} instance that owns this reader
+
+ \[[readyPromise]]
+ A promise returned by the writer's
+ {{WritableStreamDefaultWriter/ready}} getter
+
+
+Constructor, methods, and properties
+
+
+ writer = new {{WritableStreamDefaultWriter(stream)|WritableStreamDefaultWriter}}(|stream|)
+ -
+
This is equivalent to calling |stream|.{{WritableStream/getWriter()}}
.
+
+
await writer.{{WritableStreamDefaultWriter/closed}}
+ -
+
Returns a promise that will be fulfilled when the stream becomes closed, or rejected if the
+ stream ever errors or the writer's lock is [=release a write lock|released=] before the stream
+ finishes closing.
+
+
desiredSize = writer.{{WritableStreamDefaultWriter/desiredSize}}
+ -
+
Returns the [=desired size to fill a stream's internal queue|desired size to fill the stream's
+ internal queue=]. It can be negative, if the queue is over-full. A [=producer=] can use this
+ information to determine the right amount of data to write.
+
+
It will be null if the stream cannot be successfully written to (due to either being errored,
+ or having an abort queued up). It will return zero if the stream is closed. And the getter will
+ throw an exception if invoked when the writer's lock is [=release a write lock|released=].
+
+
await writer.{{WritableStreamDefaultWriter/ready}}
+ -
+
Returns a promise that will be fulfilled when the [=desired size to fill a stream's internal
+ queue|desired size to fill the stream's internal queue=] transitions from non-positive to
+ positive, signaling that it is no longer applying [=backpressure=]. Once the [=desired size to
+ fill a stream's internal queue|desired size=] dips back to zero or below, the getter will return
+ a new promise that stays pending until the next transition.
+
+
If the stream becomes errored or aborted, or the writer's lock is [=release a write
+ lock|released=], the returned promise will become rejected.
+
+
await writer.{{WritableStreamDefaultWriter/abort(reason)|abort}}([ reason ])
+ -
+
If the reader is [=active writer|active=], behaves the same as
+ |stream|.{{WritableStream/abort(reason)|abort}}(reason)
.
+
+
await writer.{{WritableStreamDefaultWriter/close()|close}}()
+ -
+
If the reader is [=active writer|active=], behaves the same as
+ |stream|.{{WritableStream/close()|close}}()
.
+
+
writer.{{WritableStreamDefaultWriter/releaseLock()|releaseLock}}()
+ -
+
[=release a write lock|Releases the writer's lock=] on the corresponding stream. After the lock
+ is released, the writer is no longer [=active writer|active=]. If the associated stream is errored
+ when the lock is released, the writer will appear errored in the same way from now on; otherwise,
the writer will appear closed.
- Note that the lock can still be released even if some ongoing writes have not yet finished (i.e. even if the promises
- returned from previous calls to {{WritableStreamDefaultWriter/write()}} have not yet settled). It's not necessary to
- hold the lock on the writer for the duration of the write; the lock instead simply prevents other producers
- from writing in an interleaved manner.
+
Note that the lock can still be released even if some ongoing writes have not yet finished
+ (i.e. even if the promises returned from previous calls to
+ {{WritableStreamDefaultWriter/write()}} have not yet settled). It's not necessary to hold the
+ lock on the writer for the duration of the write; the lock instead simply prevents other
+ [=producers=] from writing in an interleaved manner.
+
+
await writer.{{WritableStreamDefaultWriter/write(chunk)|write}}(chunk)
+ -
+
Writes the given [=chunk=] to the writable stream, by waiting until any previous writes have
+ finished successfully, and then sending the [=chunk=] to the [=underlying sink=]'s
+ {{UnderlyingSink/write|write()}} method. It will return a promise that fulfills with undefined
+ upon a successful write, or rejects if the write fails or stream becomes errored before the
+ writing process is initiated.
+
+
Note that what "success" means is up to the [=underlying sink=]; it might indicate simply that
+ the [=chunk=] has been accepted, and not necessarily that it is safely saved to its ultimate
+ destination.
+
+
+
+ The WritableStreamDefaultWriter(|stream|) constructor steps
+ are:
+
+ 1. Perform ? [$SetUpWritableStreamDefaultWriter$]([=this=], |stream|).
-
- 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, throw a *TypeError* exception.
- 1. Let _stream_ be *this*.[[ownerWritableStream]].
- 1. If _stream_ is *undefined*, return.
- 1. Assert: _stream_.[[writer]] is not *undefined*.
- 1. Perform ! WritableStreamDefaultWriterRelease(*this*).
-
+
+ The closed
+ getter steps are:
-write(chunk)
+ 1. Return [=this=].\[[closedPromise]].
+
-
- The write
method writes the given chunk to the writable stream, by waiting until any previous
- writes have finished successfully, and then sending the chunk to the underlying sink's {{underlying
- sink/write()}} method. It will return a promise that fulfills with undefined upon a successful
- write, or rejects if the write fails or stream becomes errored before the writing process is initiated.
-
- Note that what "success" means is up to the underlying sink; it might indicate simply that the chunk has
- been accepted, and not necessarily that it is safely saved to its ultimate destination.
-
-
-
- 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception.
- 1. If *this*.[[ownerWritableStream]] is *undefined*, return a promise rejected with a *TypeError* exception.
- 1. Return ! WritableStreamDefaultWriterWrite(*this*, _chunk_).
-
-
-Writable stream writer abstract operations
-
-IsWritableStreamDefaultWriter (
-x )
-
-
- 1. If Type(_x_) is not Object, return *false*.
- 1. If _x_ does not have an [[ownerWritableStream]] internal slot, return *false*.
- 1. Return *true*.
-
-
-WritableStreamDefaultWriterAbort ( writer, reason )
-
-
- 1. Let _stream_ be _writer_.[[ownerWritableStream]].
- 1. Assert: _stream_ is not *undefined*.
- 1. Return ! WritableStreamAbort(_stream_, _reason_).
-
-
-WritableStreamDefaultWriterClose ( writer )
-
-
- 1. Let _stream_ be _writer_.[[ownerWritableStream]].
- 1. Assert: _stream_ is not *undefined*.
- 1. Return ! WritableStreamClose(_stream_).
-
-
-WritableStreamDefaultWriterCloseWithErrorPropagation ( writer )
-
-This abstract operation helps implement the error propagation semantics of
-{{ReadableStream/pipeTo()}}.
-
-
- 1. Let _stream_ be _writer_.[[ownerWritableStream]].
- 1. Assert: _stream_ is not *undefined*.
- 1. Let _state_ be _stream_.[[state]].
- 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *true* or _state_ is `"closed"`, return
- a promise resolved with *undefined*.
- 1. If _state_ is `"errored"`, return a promise rejected with _stream_.[[storedError]].
- 1. Assert: _state_ is `"writable"` or `"erroring"`.
- 1. Return ! WritableStreamDefaultWriterClose(_writer_).
-
-
-WritableStreamDefaultWriterEnsureClosedPromiseRejected( writer, error )
-
-
- 1. If _writer_.[[closedPromise]].[[PromiseState]] is `"pending"`, reject _writer_.[[closedPromise]] with
- _error_.
- 1. Otherwise, set _writer_.[[closedPromise]] to a promise rejected with _error_.
- 1. Set _writer_.[[closedPromise]].[[PromiseIsHandled]] to *true*.
-
-
-WritableStreamDefaultWriterEnsureReadyPromiseRejected( writer, error )
-
-
- 1. If _writer_.[[readyPromise]].[[PromiseState]] is `"pending"`, reject _writer_.[[readyPromise]] with _error_.
- 1. Otherwise, set _writer_.[[readyPromise]] to a promise rejected with _error_.
- 1. Set _writer_.[[readyPromise]].[[PromiseIsHandled]] to *true*.
-
-
-WritableStreamDefaultWriterGetDesiredSize ( writer )
-
-
- 1. Let _stream_ be _writer_.[[ownerWritableStream]].
- 1. Let _state_ be _stream_.[[state]].
- 1. If _state_ is `"errored"` or `"erroring"`, return *null*.
- 1. If _state_ is `"closed"`, return *0*.
- 1. Return ! WritableStreamDefaultControllerGetDesiredSize(_stream_.[[writableStreamController]]).
-
-
-WritableStreamDefaultWriterRelease ( writer )
-
-
- 1. Let _stream_ be _writer_.[[ownerWritableStream]].
- 1. Assert: _stream_ is not *undefined*.
- 1. Assert: _stream_.[[writer]] is _writer_.
- 1. Let _releasedError_ be a new *TypeError*.
- 1. Perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(_writer_, _releasedError_).
- 1. Perform ! WritableStreamDefaultWriterEnsureClosedPromiseRejected(_writer_, _releasedError_).
- 1. Set _stream_.[[writer]] to *undefined*.
- 1. Set _writer_.[[ownerWritableStream]] to *undefined*.
-
-
-WritableStreamDefaultWriterWrite ( writer, chunk )
-
-
- 1. Let _stream_ be _writer_.[[ownerWritableStream]].
- 1. Assert: _stream_ is not *undefined*.
- 1. Let _controller_ be _stream_.[[writableStreamController]].
- 1. Let _chunkSize_ be ! WritableStreamDefaultControllerGetChunkSize(_controller_, _chunk_).
- 1. If _stream_ is not equal to _writer_.[[ownerWritableStream]], return a promise rejected with a *TypeError*
- exception.
- 1. Let _state_ be _stream_.[[state]].
- 1. If _state_ is `"errored"`, return a promise rejected with _stream_.[[storedError]].
- 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *true* or _state_ is `"closed"`, return
- a promise rejected with a *TypeError* exception indicating that the stream is closing or closed.
- 1. If _state_ is `"erroring"`, return a promise rejected with _stream_.[[storedError]].
- 1. Assert: _state_ is `"writable"`.
- 1. Let _promise_ be ! WritableStreamAddWriteRequest(_stream_).
- 1. Perform ! WritableStreamDefaultControllerWrite(_controller_, _chunk_, _chunkSize_).
- 1. Return _promise_.
-
-
-Class
-WritableStreamDefaultController
-
-The {{WritableStreamDefaultController}} class has methods that allow control of a {{WritableStream}}'s state. When
-constructing a {{WritableStream}}, the underlying sink is given a corresponding
-{{WritableStreamDefaultController}} instance to manipulate.
-
-Class definition
+
+ The desiredSize getter steps are:
-
+ 1. If [=this=].\[[ownerWritableStream]] is undefined, throw a {{TypeError}} exception.
+ 1. Return ! [$WritableStreamDefaultWriterGetDesiredSize$]([=this=]).
+
-This section is non-normative.
+
+ The ready getter
+ steps are:
+
+ 1. Return [=this=].\[[readyPromise]].
+
-If one were to write the {{WritableStreamDefaultController}} class in something close to the syntax of [[!ECMASCRIPT]],
-it would look like
+
+ The abort(|reason|)
+ method steps are:
-
- class WritableStreamDefaultController {
- constructor() // always throws
+ 1. If [=this=].\[[ownerWritableStream]] is undefined, return [=a promise rejected with=] a
+ {{TypeError}} exception.
+ 1. Return ! [$WritableStreamDefaultWriterAbort$]([=this=], |reason|).
+
- error(e)
- }
-
+
+ The close() method
+ steps are:
+ 1. Let |stream| be [=this=].\[[ownerWritableStream]].
+ 1. If |stream| is undefined, return [=a promise rejected with=] a {{TypeError}} exception.
+ 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is true, return [=a promise rejected
+ with=] a {{TypeError}} exception.
+ 1. Return ! [$WritableStreamDefaultWriterClose$]([=this=]).
+
+ The releaseLock() method steps are:
+
+ 1. Let |stream| be [=this=].\[[ownerWritableStream]].
+ 1. If |stream| is undefined, return.
+ 1. Assert: |stream|.\[[writer]] is not undefined.
+ 1. Perform ! [$WritableStreamDefaultWriterRelease$]([=this=]).
+
+
+
+ The write(|chunk|)
+ method steps are:
+
+ 1. If [=this=].\[[ownerWritableStream]] is undefined, return [=a promise rejected with=] a
+ {{TypeError}} exception.
+ 1. Return ! [$WritableStreamDefaultWriterWrite$]([=this=], |chunk|).
+
+
+The {{WritableStreamDefaultController}} class
+
+The {{WritableStreamDefaultController}} class has methods that allow control of a
+{{WritableStream}}'s state. When constructing a {{WritableStream}}, the [=underlying sink=] is
+given a corresponding {{WritableStreamDefaultController}} instance to manipulate.
+
+Interface definition
+
+The Web IDL definition for the {{WritableStreamDefaultController}} class is given as follows:
+
+
+[Exposed=(Window,Worker,Worklet)]
+interface WritableStreamDefaultController {
+ void error(optional any e);
+};
+
+
Internal slots
-Instances of {{WritableStreamDefaultController}} are created with the internal slots described in the following table:
+Instances of {{WritableStreamDefaultController}} are created with the internal slots described in
+the following table:
-
-
- Internal Slot
- Description (non-normative)
-
-
+
- \[[abortAlgorithm]]
- A promise-returning algorithm, taking one argument (the abort reason), which communicates
- a requested abort to the underlying sink
-
+ Internal Slot
+ Description (non-normative)
+
- \[[closeAlgorithm]]
- A promise-returning algorithm which communicates a requested close to the underlying
- sink
-
+ \[[abortAlgorithm]]
+ A promise-returning algorithm, taking one argument (the abort reason),
+ which communicates a requested abort to the [=underlying sink=]
- \[[controlledWritableStream]]
- The {{WritableStream}} instance controlled
-
+ \[[closeAlgorithm]]
+ A promise-returning algorithm which communicates a requested close to
+ the [=underlying sink=]
- \[[queue]]
- A List representing the stream's internal queue of chunks
-
+ \[[controlledWritableStream]]
+ The {{WritableStream}} instance controlled
- \[[queueTotalSize]]
- The total size of all the chunks stored in \[[queue]] (see [[#queue-with-sizes]])
-
+ \[[queue]]
+ A [=list=] representing the stream's internal queue of [=chunks=]
- \[[started]]
- A boolean flag indicating whether the underlying sink has finished starting
-
+ \[[queueTotalSize]]
+ The total size of all the chunks stored in \[[queue]] (see
+ [[#queue-with-sizes]])
- \[[strategyHWM]]
- A number supplied by the creator of the stream as part of the stream's queuing
- strategy, indicating the point at which the stream will apply backpressure to its underlying
- sink
-
+ \[[started]]
+ A boolean flag indicating whether the [=underlying sink=] has finished
+ starting
- \[[strategySizeAlgorithm]]
- An algorithm to calculate the size of enqueued chunks, as part of the stream’s
- queuing strategy
-
+ \[[strategyHWM]]
+ A number supplied by the creator of the stream as part of the stream's
+ [=queuing strategy=], indicating the point at which the stream will apply [=backpressure=] to its
+ [=underlying sink=]
- \[[writeAlgorithm]]
- A promise-returning algorithm, taking one argument (the chunk to write), which
- writes data to the underlying sink
-
+ \[[strategySizeAlgorithm]]
+ An algorithm to calculate the size of enqueued [=chunks=], as part of
+ the stream's [=queuing strategy=]
+
+ \[[writeAlgorithm]]
+ A promise-returning algorithm, taking one argument (the [=chunk=] to
+ write), which writes data to the [=underlying sink=]
-new WritableStreamDefaultController()
+Methods
-
- The WritableStreamDefaultController
constructor cannot be used directly;
- {{WritableStreamDefaultController}} instances are created automatically during {{WritableStream}} construction.
+
+ controller.{{WritableStreamDefaultController/error()|error}}(e)
+ -
+
Closes the controlled writable stream, making all future interactions with it fail with the
+ given error e.
+
+
This method is rarely used, since usually it suffices to return a rejected promise from one of
+ the [=underlying sink=]'s methods. However, it can be useful for suddenly shutting down a stream
+ in response to an event outside the normal lifecycle of interactions with the [=underlying
+ sink=].
+
+
+
+ The error(|e|) method steps are:
+
+ 1. Let |state| be [=this=].\[[controlledWritableStream]].\[[state]].
+ 1. If |state| is not "`writable`", return.
+ 1. Perform ! [$WritableStreamDefaultControllerError$]([=this=], |e|).
-
- 1. Throw a *TypeError* exception.
-
+Internal methods
-Properties of the {{WritableStreamDefaultController}} prototype
+The following are internal methods implemented by each {{WritableStreamDefaultController}} instance.
+The writable stream implementation will call into these.
-error(e)
+The reason these are in method form, instead of as abstract operations, is to make
+it clear that the writable stream implementation is decoupled from the controller implementation,
+and could in the future be expanded with other controllers, as long as those controllers
+implemented such internal methods. A similar scenario is seen for readable streams (see
+[[#rs-abstract-ops-used-by-controllers]]), where there actually are multiple controller types and
+as such the counterpart internal methods are used polymorphically.
-
- The error
method will error the writable stream, making all future interactions with it fail with the
- given error e
.
-
- This method is rarely used, since usually it suffices to return a rejected promise from one of the underlying
- sink's methods. However, it can be useful for suddenly shutting down a stream in response to an event outside the
- normal lifecycle of interactions with the underlying sink.
-
-
-
- 1. If ! IsWritableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception.
- 1. Let _state_ be *this*.[[controlledWritableStream]].[[state]].
- 1. If _state_ is not `"writable"`, return.
- 1. Perform ! WritableStreamDefaultControllerError(*this*, _e_).
-
-
-Writable stream default controller internal methods
-
-The following are additional internal methods implemented by each {{WritableStreamDefaultController}} instance. The
-writable stream implementation will call into these.
-
-The reason these are in method form, instead of as abstract operations, is to make it clear that the
-writable stream implementation is decoupled from the controller implementation, and could in the future be expanded with
-other controllers, as long as those controllers implemented such internal methods. A similar scenario is seen for
-readable streams, where there actually are multiple controller types and as such the counterpart internal methods are
-used polymorphically.
-
-
\[[AbortSteps]](
-reason )
-
-
- 1. Let _result_ be the result of performing *this*.[[abortAlgorithm]], passing _reason_.
- 1. Perform ! WritableStreamDefaultControllerClearAlgorithms(*this*).
- 1. Return _result_.
-
-
-\[[ErrorSteps]]()
-
-
- 1. Perform ! ResetQueue(*this*).
-
-
-Writable stream default controller abstract operations
-
-IsWritableStreamDefaultController ( x )
-
-
- 1. If Type(_x_) is not Object, return *false*.
- 1. If _x_ does not have an [[controlledWritableStream]] internal slot, return *false*.
- 1. Return *true*.
-
-
-SetUpWritableStreamDefaultController ( stream, controller, startAlgorithm,
-writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm )
-
-
- 1. Assert: ! IsWritableStream(_stream_) is *true*.
- 1. Assert: _stream_.[[writableStreamController]] is *undefined*.
- 1. Set _controller_.[[controlledWritableStream]] to _stream_.
- 1. Set _stream_.[[writableStreamController]] to _controller_.
- 1. Perform ! ResetQueue(_controller_).
- 1. Set _controller_.[[started]] to *false*.
- 1. Set _controller_.[[strategySizeAlgorithm]] to _sizeAlgorithm_.
- 1. Set _controller_.[[strategyHWM]] to _highWaterMark_.
- 1. Set _controller_.[[writeAlgorithm]] to _writeAlgorithm_.
- 1. Set _controller_.[[closeAlgorithm]] to _closeAlgorithm_.
- 1. Set _controller_.[[abortAlgorithm]] to _abortAlgorithm_.
- 1. Let _backpressure_ be ! WritableStreamDefaultControllerGetBackpressure(_controller_).
- 1. Perform ! WritableStreamUpdateBackpressure(_stream_, _backpressure_).
- 1. Let _startResult_ be the result of performing _startAlgorithm_. (This may throw an exception.)
- 1. Let _startPromise_ be a promise resolved with _startResult_.
- 1. Upon fulfillment of _startPromise_,
- 1. Assert: _stream_.[[state]] is `"writable"` or `"erroring"`.
- 1. Set _controller_.[[started]] to *true*.
- 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_).
- 1. Upon rejection of _startPromise_ with reason _r_,
- 1. Assert: _stream_.[[state]] is `"writable"` or `"erroring"`.
- 1. Set _controller_.[[started]] to *true*.
- 1. Perform ! WritableStreamDealWithRejection(_stream_, _r_).
-
-
-SetUpWritableStreamDefaultControllerFromUnderlyingSink ( stream, underlyingSink,
-highWaterMark, sizeAlgorithm )
-
-
- 1. Assert: _underlyingSink_ is not *undefined*.
- 1. Let _controller_ be ObjectCreate(the original value of `WritableStreamDefaultController`'s `prototype`
- property).
- 1. Let _startAlgorithm_ be the following steps:
- 1. Return ? InvokeOrNoop(_underlyingSink_, `"start"`, « _controller_ »).
- 1. Let _writeAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_underlyingSink_, `"write"`, *1*, « _controller_ »).
- 1. Let _closeAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_underlyingSink_, `"close"`, *0*, « »).
- 1. Let _abortAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_underlyingSink_, `"abort"`, *1*, « »).
- 1. Perform ? SetUpWritableStreamDefaultController(_stream_, _controller_, _startAlgorithm_, _writeAlgorithm_,
- _closeAlgorithm_, _abortAlgorithm_, _highWaterMark_, _sizeAlgorithm_).
-
-
-WritableStreamDefaultControllerClearAlgorithms ( controller )
-
-This abstract operation is called once the stream is closed or errored and the algorithms will not be executed any more.
-By removing the algorithm references it permits the underlying sink object to be garbage collected even if the
-{{WritableStream}} itself is still referenced.
-
-The results of this algorithm are not currently observable, but could become so if JavaScript eventually
-adds weak references. But even without that factor,
-implementations will likely want to include similar steps.
-
-This operation will be performed multiple times in some edge cases. After the first time it will do
-nothing.
-
-
- 1. Set _controller_.[[writeAlgorithm]] to *undefined*.
- 1. Set _controller_.[[closeAlgorithm]] to *undefined*.
- 1. Set _controller_.[[abortAlgorithm]] to *undefined*.
- 1. Set _controller_.[[strategySizeAlgorithm]] to *undefined*.
-
-
-WritableStreamDefaultControllerClose ( controller )
-
-
- 1. Perform ! EnqueueValueWithSize(_controller_, `"close"`, *0*).
- 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_).
-
-
-WritableStreamDefaultControllerGetChunkSize ( controller, chunk )
-
-
- 1. Let _returnValue_ be the result of performing _controller_.[[strategySizeAlgorithm]], passing in _chunk_, and
- interpreting the result as an ECMAScript completion value.
- 1. If _returnValue_ is an abrupt completion,
- 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(_controller_, _returnValue_.[[Value]]).
- 1. Return 1.
- 1. Return _returnValue_.[[Value]].
-
-
-WritableStreamDefaultControllerGetDesiredSize ( controller )
-
-
- 1. Return _controller_.[[strategyHWM]] − _controller_.[[queueTotalSize]].
-
-
-WritableStreamDefaultControllerWrite ( controller, chunk, chunkSize
-)
-
-
- 1. Let _writeRecord_ be Record {[[chunk]]: _chunk_}.
- 1. Let _enqueueResult_ be EnqueueValueWithSize(_controller_, _writeRecord_, _chunkSize_).
- 1. If _enqueueResult_ is an abrupt completion,
- 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(_controller_, _enqueueResult_.[[Value]]).
- 1. Return.
- 1. Let _stream_ be _controller_.[[controlledWritableStream]].
- 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false* and _stream_.[[state]] is `"writable"`,
- 1. Let _backpressure_ be ! WritableStreamDefaultControllerGetBackpressure(_controller_).
- 1. Perform ! WritableStreamUpdateBackpressure(_stream_, _backpressure_).
- 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_).
-
-
-WritableStreamDefaultControllerAdvanceQueueIfNeeded (
-controller )
-
-
- 1. Let _stream_ be _controller_.[[controlledWritableStream]].
- 1. If _controller_.[[started]] is *false*, return.
- 1. If _stream_.[[inFlightWriteRequest]] is not *undefined*, return.
- 1. Let _state_ be _stream_.[[state]].
- 1. Assert: _state_ is not `"closed"` or `"errored"`.
- 1. If _state_ is `"erroring"`,
- 1. Perform ! WritableStreamFinishErroring(_stream_).
- 1. Return.
- 1. If _controller_.[[queue]] is empty, return.
- 1. Let _writeRecord_ be ! PeekQueueValue(_controller_).
- 1. If _writeRecord_ is `"close"`, perform ! WritableStreamDefaultControllerProcessClose(_controller_).
- 1. Otherwise, perform ! WritableStreamDefaultControllerProcessWrite(_controller_, _writeRecord_.[[chunk]]).
-
-
-WritableStreamDefaultControllerErrorIfNeeded ( controller, error )
-
-
- 1. If _controller_.[[controlledWritableStream]].[[state]] is `"writable"`, perform !
- WritableStreamDefaultControllerError(_controller_, _error_).
-
-
-WritableStreamDefaultControllerProcessClose ( controller )
-
-
- 1. Let _stream_ be _controller_.[[controlledWritableStream]].
- 1. Perform ! WritableStreamMarkCloseRequestInFlight(_stream_).
- 1. Perform ! DequeueValue(_controller_).
- 1. Assert: _controller_.[[queue]] is empty.
- 1. Let _sinkClosePromise_ be the result of performing _controller_.[[closeAlgorithm]].
- 1. Perform ! WritableStreamDefaultControllerClearAlgorithms(_controller_).
- 1. Upon fulfillment of _sinkClosePromise_,
- 1. Perform ! WritableStreamFinishInFlightClose(_stream_).
- 1. Upon rejection of _sinkClosePromise_ with reason _reason_,
- 1. Perform ! WritableStreamFinishInFlightCloseWithError(_stream_, _reason_).
-
-
-WritableStreamDefaultControllerProcessWrite ( controller, chunk )
-
-
- 1. Let _stream_ be _controller_.[[controlledWritableStream]].
- 1. Perform ! WritableStreamMarkFirstWriteRequestInFlight(_stream_).
- 1. Let _sinkWritePromise_ be the result of performing _controller_.[[writeAlgorithm]], passing in _chunk_.
- 1. Upon fulfillment of _sinkWritePromise_,
- 1. Perform ! WritableStreamFinishInFlightWrite(_stream_).
- 1. Let _state_ be _stream_.[[state]].
- 1. Assert: _state_ is `"writable"` or `"erroring"`.
- 1. Perform ! DequeueValue(_controller_).
- 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false* and _state_ is `"writable"`,
- 1. Let _backpressure_ be ! WritableStreamDefaultControllerGetBackpressure(_controller_).
- 1. Perform ! WritableStreamUpdateBackpressure(_stream_, _backpressure_).
- 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_).
- 1. Upon rejection of _sinkWritePromise_ with _reason_,
- 1. If _stream_.[[state]] is `"writable"`, perform ! WritableStreamDefaultControllerClearAlgorithms(_controller_).
- 1. Perform ! WritableStreamFinishInFlightWriteWithError(_stream_, _reason_).
-
-
-WritableStreamDefaultControllerGetBackpressure ( controller )
-
-
- 1. Let _desiredSize_ be ! WritableStreamDefaultControllerGetDesiredSize(_controller_).
- 1. Return _desiredSize_ ≤ *0*.
-
-
-WritableStreamDefaultControllerError ( controller, error )
-
-
- 1. Let _stream_ be _controller_.[[controlledWritableStream]].
- 1. Assert: _stream_.[[state]] is `"writable"`.
- 1. Perform ! WritableStreamDefaultControllerClearAlgorithms(_controller_).
- 1. Perform ! WritableStreamStartErroring(_stream_, _error_).
-
+
+ \[[AbortSteps]](|reason|) implements the
+ [$WritableStreamController/[[AbortSteps]]$] contract. It performs the following steps:
+
+ 1. Let |result| be the result of performing [=this=].\[[abortAlgorithm]], passing |reason|.
+ 1. Perform ! [$WritableStreamDefaultControllerClearAlgorithms$]([=this=]).
+ 1. Return |result|.
+
+
+
+ \[[ErrorSteps]]() implements the
+ [$WritableStreamController/[[ErrorSteps]]$] contract. It performs the following steps:
+
+ 1. Perform ! ResetQueue([=this=]).
+
+
+Abstract operations
+
+Working with writable streams
+
+The following abstract operations operate on {{WritableStream}} instances at a higher level. Some
+are even meant to be generally useful by other specifications.
+
+
+ AcquireWritableStreamDefaultWriter(|stream|) is meant to be called from other
+ specifications that wish to acquire a [=writer=] for a given writable stream. It performs the
+ following steps:
+
+ 1. Let |writer| be a [=new=] {{WritableStreamDefaultWriter}}.
+ 1. Perform ? [$SetUpWritableStreamDefaultWriter$](|writer|, |stream|).
+ 1. Return |writer|.
+
+
+
+ CreateWritableStream(|startAlgorithm|, |writeAlgorithm|, |closeAlgorithm|,
+ |abortAlgorithm|[, |highWaterMark|[, |sizeAlgorithm|]]) is meant to be called from other
+ specifications that wish to create {{WritableStream}} instances. The |writeAlgorithm|,
+ |closeAlgorithm|, and |abortAlgorithm| algorithms must return promises; if supplied,
+ |sizeAlgorithm| must be an algorithm accepting [=chunk=] objects and returning a number; and if
+ supplied, |highWaterMark| must be a non-negative, non-NaN number.
+
+ It performs the following steps:
+
+ 1. If |highWaterMark| was not passed, set it to 1.
+ 1. If |sizeAlgorithm| was not passed, set it to an algorithm that returns 1.
+ 1. Assert: ! [$IsNonNegativeNumber$](|highWaterMark|) is true.
+ 1. Let |stream| be a [=new=] {{WritableStream}}.
+ 1. Perform ! [$InitializeWritableStream$](|stream|).
+ 1. Let |controller| be a [=new=] {{WritableStreamDefaultController}}.
+ 1. Perform ? [$SetUpWritableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|,
+ |writeAlgorithm|, |closeAlgorithm|, |abortAlgorithm|, |highWaterMark|, |sizeAlgorithm|).
+ 1. Return |stream|.
+
+ This abstract operation will throw an exception if and only if the supplied
+ |startAlgorithm| throws.
+
+
+
+ InitializeWritableStream(|stream|) performs the following
+ steps:
+
+ 1. Set |stream|.\[[state]] to "`writable`".
+ 1. Set |stream|.\[[storedError]], |stream|.\[[writer]], |stream|.\[[writableStreamController]],
+ |stream|.\[[inFlightWriteRequest]], |stream|.\[[closeRequest]],
+ |stream|.\[[inFlightCloseRequest]] and |stream|.\[[pendingAbortRequest]] to undefined.
+ 1. Set |stream|.\[[writeRequests]] to a new empty [=list=].
+ 1. Set |stream|.\[[backpressure]] to false.
+
+
+
+ IsWritableStreamLocked(|stream|) is meant to be called from
+ other specifications that wish to query whether or not a writable stream is [=locked to a writer=].
+ It performs the following steps:
+
+ 1. If |stream|.\[[writer]] is undefined, return false.
+ 1. Return true.
+
+
+
+ SetUpWritableStreamDefaultWriter(|writer|,
+ |stream|) performs the following steps:
+
+ 1. If ! [$IsWritableStreamLocked$](|stream|) is true, throw a {{TypeError}} exception.
+ 1. Set |writer|.\[[ownerWritableStream]] to |stream|.
+ 1. Set |stream|.\[[writer]] to |writer|.
+ 1. Let |state| be |stream|.\[[state]].
+ 1. If |state| is "`writable`",
+ 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false and |stream|.\[[backpressure]]
+ is true, set |writer|.\[[readyPromise]] to [=a new promise=].
+ 1. Otherwise, set |writer|.\[[readyPromise]] to [=a promise resolved with=] undefined.
+ 1. Set |writer|.\[[closedPromise]] to [=a new promise=].
+ 1. Otherwise, if |state| is "`erroring`",
+ 1. Set |writer|.\[[readyPromise]] to [=a promise rejected with=] |stream|.\[[storedError]].
+ 1. Set |writer|.\[[readyPromise]].\[[PromiseIsHandled]] to true.
+ 1. Set |writer|.\[[closedPromise]] to [=a new promise=].
+ 1. Otherwise, if |state| is "`closed`",
+ 1. Set |writer|.\[[readyPromise]] to [=a promise resolved with=] undefined.
+ 1. Set |writer|.\[[closedPromise]] to [=a promise resolved with=] undefined.
+ 1. Otherwise,
+ 1. Assert: |state| is "`errored`".
+ 1. Let |storedError| be |stream|.\[[storedError]].
+ 1. Set |writer|.\[[readyPromise]] to [=a promise rejected with=] |storedError|.
+ 1. Set |writer|.\[[readyPromise]].\[[PromiseIsHandled]] to true.
+ 1. Set |writer|.\[[closedPromise]] to [=a promise rejected with=] |storedError|.
+ 1. Set |writer|.\[[closedPromise]].\[[PromiseIsHandled]] to true.
+
+
+
+ WritableStreamAbort(|stream|, |reason|) performs the following
+ steps:
+
+ 1. Let |state| be |stream|.\[[state]].
+ 1. If |state| is "`closed"` or `"errored`", return [=a promise resolved with=] undefined.
+ 1. If |stream|.\[[pendingAbortRequest]] is not undefined, return
+ |stream|.\[[pendingAbortRequest]].\[[promise]].
+ 1. Assert: |state| is "`writable"` or `"erroring`".
+ 1. Let |wasAlreadyErroring| be false.
+ 1. If |state| is "`erroring`",
+ 1. Set |wasAlreadyErroring| to true.
+ 1. Set |reason| to undefined.
+ 1. Let |promise| be [=a new promise=].
+ 1. Set |stream|.\[[pendingAbortRequest]] to Record {\[[promise]]: |promise|, \[[reason]]: |reason|,
+ \[[wasAlreadyErroring]]: |wasAlreadyErroring|}.
+ 1. If |wasAlreadyErroring| is false, perform ! [$WritableStreamStartErroring$](|stream|, |reason|).
+ 1. Return |promise|.
+
+
+
+ WritableStreamClose(|stream|) performs the following steps:
+
+ 1. Let |state| be |stream|.\[[state]].
+ 1. If |state| is "`closed"` or `"errored`", return [=a promise rejected with=] a {{TypeError}}
+ exception.
+ 1. Assert: |state| is "`writable"` or `"erroring`".
+ 1. Assert: ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false.
+ 1. Let |promise| be [=a new promise=].
+ 1. Set |stream|.\[[closeRequest]] to |promise|.
+ 1. Let |writer| be |stream|.\[[writer]].
+ 1. If |writer| is not undefined, and |stream|.\[[backpressure]] is true, and |state| is
+ "`writable`", [=resolve=] |writer|.\[[readyPromise]] with undefined.
+ 1. Perform ! [$WritableStreamDefaultControllerClose$](|stream|.\[[writableStreamController]]).
+ 1. Return |promise|.
+
+
+Interfacing with controllers
+
+To allow future flexibility to add different writable stream behaviors (similar to the distinction
+between default readable streams and [=readable byte streams=]), much of the internal state of a
+[=writable stream=] is encapsulated by the {{WritableStreamDefaultController}} class.
+
+Each controller class defines two internal methods, which are called by the {{WritableStream}}
+algorithms:
+
+
+ - \[[AbortSteps]](reason)
+
- The controller's steps that run in reaction to the stream being [=abort a writable
+ stream|aborted=], used to clean up the state stored in the controller and inform the
+ [=underlying sink=].
+
+
- \[[ErrorSteps]]()
+
- The controller's steps that run in reaction to the stream being errored, used to clean up the
+ state stored in the controller.
+
+
+(These are defined as internal methods, instead of as abstract operations, so that they can be
+called polymorphically by the {{WritableStream}} algorithms, without having to branch on which type
+of controller is present. This is a bit theoretical for now, given that only
+{{WritableStreamDefaultController}} exists so far.)
+
+The rest of this section concerns abstract operations that go in the other direction: they are used
+by the controller implementation to affect its associated {{WritableStream}} object. This
+translates internal state changes of the controllerinto developer-facing results visible through
+the {{WritableStream}}'s public API.
+
+
+ WritableStreamAddWriteRequest(|stream|) performs the
+ following steps:
+
+ 1. Assert: ! [$IsWritableStreamLocked$](|stream|) is true.
+ 1. Assert: |stream|.\[[state]] is "`writable`".
+ 1. Let |promise| be [=a new promise=].
+ 1. [=list/Append=] |promise| to |stream|.\[[writeRequests]].
+ 1. Return |promise|.
+
+
+
+ WritableStreamCloseQueuedOrInFlight(|stream|)
+ performs the following steps:
+
+ 1. If |stream|.\[[closeRequest]] is undefined and |stream|.\[[inFlightCloseRequest]] is undefined,
+ return false.
+ 1. Return true.
+
+
+
+ WritableStreamDealWithRejection(|stream|, |error|)
+ performs the following steps:
+
+ 1. Let |state| be |stream|.\[[state]].
+ 1. If |state| is "`writable`",
+ 1. Perform ! [$WritableStreamStartErroring$](|stream|, |error|).
+ 1. Return.
+ 1. Assert: |state| is "`erroring`".
+ 1. Perform ! [$WritableStreamFinishErroring$](|stream|).
+
+
+
+ WritableStreamFinishErroring(|stream|, |reason|)
+ performs the following steps:
+
+ 1. Assert: |stream|.\[[state]] is "`erroring`".
+ 1. Assert: ! [$WritableStreamHasOperationMarkedInFlight$](|stream|) is false.
+ 1. Set |stream|.\[[state]] to "`errored`".
+ 1. Perform ! |stream|.\[[writableStreamController]].\[[ErrorSteps]]().
+ 1. Let |storedError| be |stream|.\[[storedError]].
+ 1. [=list/For each=] |writeRequest| of |stream|.\[[writeRequests]]:
+ 1. [=Reject=] |writeRequest| with |storedError|.
+ 1. Set |stream|.\[[writeRequests]] to an empty [=list=].
+ 1. If |stream|.\[[pendingAbortRequest]] is undefined,
+ 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|).
+ 1. Return.
+ 1. Let |abortRequest| be |stream|.\[[pendingAbortRequest]].
+ 1. Set |stream|.\[[pendingAbortRequest]] to undefined.
+ 1. If |abortRequest|.\[[wasAlreadyErroring]] is true,
+ 1. [=Reject=] |abortRequest|.\[[promise]] with |storedError|.
+ 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|).
+ 1. Return.
+ 1. Let |promise| be !
+ stream.\[[writableStreamController]].\[[AbortSteps]](|abortRequest|.\[[reason]]).
+ 1. [=Upon fulfillment=] of |promise|,
+ 1. [=Resolve=] |abortRequest|.\[[promise]] with undefined.
+ 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|).
+ 1. [=Upon rejection=] of |promise| with reason |reason|,
+ 1. [=Reject=] |abortRequest|.\[[promise]] with |reason|.
+ 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|).
+
+
+
+ WritableStreamFinishInFlightClose(|stream|)
+ performs the following steps:
+
+ 1. Assert: |stream|.\[[inFlightCloseRequest]] is not undefined.
+ 1. [=Resolve=] |stream|.\[[inFlightCloseRequest]] with undefined.
+ 1. Set |stream|.\[[inFlightCloseRequest]] to undefined.
+ 1. Let |state| be |stream|.\[[state]].
+ 1. Assert: |stream|.\[[state]] is "`writable"` or `"erroring`".
+ 1. If |state| is "`erroring`",
+ 1. Set |stream|.\[[storedError]] to undefined.
+ 1. If |stream|.\[[pendingAbortRequest]] is not undefined,
+ 1. [=Resolve=] |stream|.\[[pendingAbortRequest]].\[[promise]] with undefined.
+ 1. Set |stream|.\[[pendingAbortRequest]] to undefined.
+ 1. Set |stream|.\[[state]] to "`closed`".
+ 1. Let |writer| be |stream|.\[[writer]].
+ 1. If |writer| is not undefined, [=resolve=] |writer|.\[[closedPromise]] with undefined.
+ 1. Assert: |stream|.\[[pendingAbortRequest]] is undefined.
+ 1. Assert: |stream|.\[[storedError]] is undefined.
+
+
+
+ WritableStreamFinishInFlightCloseWithError(|stream|,
+ |error|) performs the following steps:
+
+ 1. Assert: |stream|.\[[inFlightCloseRequest]] is not undefined.
+ 1. [=Reject=] |stream|.\[[inFlightCloseRequest]] with |error|.
+ 1. Set |stream|.\[[inFlightCloseRequest]] to undefined.
+ 1. Assert: |stream|.\[[state]] is "`writable"` or `"erroring`".
+ 1. If |stream|.\[[pendingAbortRequest]] is not undefined,
+ 1. [=Reject=] |stream|.\[[pendingAbortRequest]].\[[promise]] with |error|.
+ 1. Set |stream|.\[[pendingAbortRequest]] to undefined.
+ 1. Perform ! WritableStreamDealWithRejection(|stream|, |error|).
+
+
+
+ WritableStreamFinishInFlightWrite(|stream|)
+ performs the following steps:
+
+ 1. Assert: |stream|.\[[inFlightWriteRequest]] is not undefined.
+ 1. [=Resolve=] |stream|.\[[inFlightWriteRequest]] with undefined.
+ 1. Set |stream|.\[[inFlightWriteRequest]] to undefined.
+
+
+
+ WritableStreamFinishInFlightWriteWithError(|stream|,
+ |error|) performs the following steps:
+
+ 1. Assert: |stream|.\[[inFlightWriteRequest]] is not undefined.
+ 1. [=Reject=] |stream|.\[[inFlightWriteRequest]] with |error|.
+ 1. Set |stream|.\[[inFlightWriteRequest]] to undefined.
+ 1. Assert: |stream|.\[[state]] is "`writable"` or `"erroring`".
+ 1. Perform ! [$WritableStreamDealWithRejection$](|stream|, |error|).
+
+
+
+ WritableStreamHasOperationMarkedInFlight(|stream|)
+ performs the following steps:
+
+ 1. If |stream|.\[[inFlightWriteRequest]] is undefined and
+ |stream|.\[[writableStreamController]].\[[inFlightCloseRequest]] is undefined, return false.
+ 1. Return true.
+
+
+
+ WritableStreamMarkCloseRequestInFlight(|stream|)
+ performs the following steps:
+
+ 1. Assert: |stream|.\[[inFlightCloseRequest]] is undefined.
+ 1. Assert: |stream|.\[[closeRequest]] is not undefined.
+ 1. Set |stream|.\[[inFlightCloseRequest]] to |stream|.\[[closeRequest]].
+ 1. Set |stream|.\[[closeRequest]] to undefined.
+
+
+
+ WritableStreamMarkFirstWriteRequestInFlight(|stream|)
+ performs the following steps:
+
+ 1. Assert: |stream|.\[[inFlightWriteRequest]] is undefined.
+ 1. Assert: |stream|.\[[writeRequests]] is not empty.
+ 1. Let |writeRequest| be |stream|.\[[writeRequests]][0].
+ 1. [=list/Remove=] |writeRequest| from |stream|.\[[writeRequests]].
+ 1. Set |stream|.\[[inFlightWriteRequest]] to |writeRequest|.
+
+
+
+ WritableStreamRejectCloseAndClosedPromiseIfNeeded(|stream|)
+ performs the following steps:
+
+ 1. Assert: |stream|.\[[state]] is "`errored`".
+ 1. If |stream|.\[[closeRequest]] is not undefined,
+ 1. Assert: |stream|.\[[inFlightCloseRequest]] is undefined.
+ 1. [=Reject=] |stream|.\[[closeRequest]] with |stream|.\[[storedError]].
+ 1. Set |stream|.\[[closeRequest]] to undefined.
+ 1. Let |writer| be |stream|.\[[writer]].
+ 1. If |writer| is not undefined,
+ 1. [=Reject=] |writer|.\[[closedPromise]] with |stream|.\[[storedError]].
+ 1. Set |writer|.\[[closedPromise]].\[[PromiseIsHandled]] to true.
+
+
+
+ WritableStreamStartErroring(|stream|, |reason|)
+ performs the following steps:
+
+ 1. Assert: |stream|.\[[storedError]] is undefined.
+ 1. Assert: |stream|.\[[state]] is "`writable`".
+ 1. Let |controller| be |stream|.\[[writableStreamController]].
+ 1. Assert: |controller| is not undefined.
+ 1. Set |stream|.\[[state]] to "`erroring`".
+ 1. Set |stream|.\[[storedError]] to |reason|.
+ 1. Let |writer| be |stream|.\[[writer]].
+ 1. If |writer| is not undefined, perform !
+ [$WritableStreamDefaultWriterEnsureReadyPromiseRejected$](|writer|, |reason|).
+ 1. If ! [$WritableStreamHasOperationMarkedInFlight$](|stream|) is false and
+ |controller|.\[[started]] is true, perform ! [$WritableStreamFinishErroring$](|stream|).
+
+
+
+ WritableStreamUpdateBackpressure(|stream|,
+ |backpressure|) performs the following steps:
+
+ 1. Assert: |stream|.\[[state]] is "`writable`".
+ 1. Assert: ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false.
+ 1. Let |writer| be |stream|.\[[writer]].
+ 1. If |writer| is not undefined and |backpressure| is not |stream|.\[[backpressure]],
+ 1. If |backpressure| is true, set |writer|.\[[readyPromise]] to [=a new promise=].
+ 1. Otherwise,
+ 1. Assert: |backpressure| is false.
+ 1. [=Resolve=] |writer|.\[[readyPromise]] with undefined.
+ 1. Set |stream|.\[[backpressure]] to |backpressure|.
+
+
+Writers
+
+The following abstract operations support the implementation and manipualtion of
+{{WritableStreamDefaultWriter}} instances.
+
+
+ WritableStreamDefaultWriterAbort(|writer|,
+ |reason|) performs the following steps:
+
+ 1. Let |stream| be |writer|.\[[ownerWritableStream]].
+ 1. Assert: |stream| is not undefined.
+ 1. Return ! [$WritableStreamAbort$](|stream|, |reason|).
+
+
+
+ WritableStreamDefaultWriterClose(|writer|) performs
+ the following steps:
+
+ 1. Let |stream| be |writer|.\[[ownerWritableStream]].
+ 1. Assert: |stream| is not undefined.
+ 1. Return ! [$WritableStreamClose$](|stream|).
+
+
+
+ WritableStreamDefaultWriterCloseWithErrorPropagation(|writer|)
+ performs the following steps:
+
+ 1. Let |stream| be |writer|.\[[ownerWritableStream]].
+ 1. Assert: |stream| is not undefined.
+ 1. Let |state| be |stream|.\[[state]].
+ 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is true or |state| is "`closed`", return
+ [=a promise resolved with=] undefined.
+ 1. If |state| is "`errored`", return [=a promise rejected with=] |stream|.\[[storedError]].
+ 1. Assert: |state| is "`writable"` or `"erroring`".
+ 1. Return ! [$WritableStreamDefaultWriterClose$](|writer|).
+
+ This abstract operation helps implement the error propagation semantics of
+ {{ReadableStream}}'s {{ReadableStream/pipeTo()}}.
+
+
+
+ WritableStreamDefaultWriterEnsureClosedPromiseRejected(|writer|,
+ |error|) performs the following steps:
+
+ 1. If |writer|.\[[closedPromise]].\[[PromiseState]] is "`pending`", [=reject=]
+ |writer|.\[[closedPromise]] with |error|.
+ 1. Otherwise, set |writer|.\[[closedPromise]] to [=a promise rejected with=] |error|.
+ 1. Set |writer|.\[[closedPromise]].\[[PromiseIsHandled]] to true.
+
+
+
+ WritableStreamDefaultWriterEnsureReadyPromiseRejected(|writer|,
+ |error|) performs the following steps:
+
+ 1. If |writer|.\[[readyPromise]].\[[PromiseState]] is "`pending`", [=reject=]
+ |writer|.\[[readyPromise]] with |error|.
+ 1. Otherwise, set |writer|.\[[readyPromise]] to [=a promise rejected with=] |error|.
+ 1. Set |writer|.\[[readyPromise]].\[[PromiseIsHandled]] to true.
+
+
+
+ WritableStreamDefaultWriterGetDesiredSize(|writer|)
+ performs the following steps:
+
+ 1. Let |stream| be |writer|.\[[ownerWritableStream]].
+ 1. Let |state| be |stream|.\[[state]].
+ 1. If |state| is "`errored"` or `"erroring`", return null.
+ 1. If |state| is "`closed`", return 0.
+ 1. Return !
+ [$WritableStreamDefaultControllerGetDesiredSize$](|stream|.\[[writableStreamController]]).
+
+
+
+ WritableStreamDefaultWriterRelease(|writer|)
+ performs the following steps:
+
+ 1. Let |stream| be |writer|.\[[ownerWritableStream]].
+ 1. Assert: |stream| is not undefined.
+ 1. Assert: |stream|.\[[writer]] is |writer|.
+ 1. Let |releasedError| be a new {{TypeError}}.
+ 1. Perform ! [$WritableStreamDefaultWriterEnsureReadyPromiseRejected$](|writer|, |releasedError|).
+ 1. Perform ! [$WritableStreamDefaultWriterEnsureClosedPromiseRejected$](|writer|, |releasedError|).
+ 1. Set |stream|.\[[writer]] to undefined.
+ 1. Set |writer|.\[[ownerWritableStream]] to undefined.
+
+
+
+ WritableStreamDefaultWriterWrite(|writer|, |chunk|)
+ performs the following steps:
+
+ 1. Let |stream| be |writer|.\[[ownerWritableStream]].
+ 1. Assert: |stream| is not undefined.
+ 1. Let |controller| be |stream|.\[[writableStreamController]].
+ 1. Let |chunkSize| be ! [$WritableStreamDefaultControllerGetChunkSize$](|controller|, |chunk|).
+ 1. If |stream| is not equal to |writer|.\[[ownerWritableStream]], return [=a promise rejected
+ with=] a {{TypeError}} exception.
+ 1. Let |state| be |stream|.\[[state]].
+ 1. If |state| is "`errored`", return [=a promise rejected with=] |stream|.\[[storedError]].
+ 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is true or |state| is "`closed`", return
+ [=a promise rejected with=] a {{TypeError}} exception indicating that the stream is closing or
+ closed.
+ 1. If |state| is "`erroring`", return [=a promise rejected with=] |stream|.\[[storedError]].
+ 1. Assert: |state| is "`writable`".
+ 1. Let |promise| be ! [$WritableStreamAddWriteRequest$](|stream|).
+ 1. Perform ! [$WritableStreamDefaultControllerWrite$](|controller|, |chunk|, |chunkSize|).
+ 1. Return |promise|.
+
+
+Default controllers
+
+The following abstract operations support the implementation of the
+{{WritableStreamDefaultController}} class.
+
+
+
+ SetUpWritableStreamDefaultController(|stream|,
+ |controller|, |startAlgorithm|, |writeAlgorithm|, |closeAlgorithm|, |abortAlgorithm|,
+ |highWaterMark|, |sizeAlgorithm|) performs the following steps:
+
+ 1. Assert: |stream| [=implements=] {{WritableStream}}.
+ 1. Assert: |stream|.\[[writableStreamController]] is undefined.
+ 1. Set |controller|.\[[controlledWritableStream]] to |stream|.
+ 1. Set |stream|.\[[writableStreamController]] to |controller|.
+ 1. Perform ! [$ResetQueue$](|controller|).
+ 1. Set |controller|.\[[started]] to false.
+ 1. Set |controller|.\[[strategySizeAlgorithm]] to |sizeAlgorithm|.
+ 1. Set |controller|.\[[strategyHWM]] to |highWaterMark|.
+ 1. Set |controller|.\[[writeAlgorithm]] to |writeAlgorithm|.
+ 1. Set |controller|.\[[closeAlgorithm]] to |closeAlgorithm|.
+ 1. Set |controller|.\[[abortAlgorithm]] to |abortAlgorithm|.
+ 1. Let |backpressure| be ! [$WritableStreamDefaultControllerGetBackpressure$](|controller|).
+ 1. Perform ! [$WritableStreamUpdateBackpressure$](|stream|, |backpressure|).
+ 1. Let |startResult| be the result of performing |startAlgorithm|. (This may throw an exception.)
+ 1. Let |startPromise| be [=a promise resolved with=] |startResult|.
+ 1. [=Upon fulfillment=] of |startPromise|,
+ 1. Assert: |stream|.\[[state]] is "`writable"` or `"erroring`".
+ 1. Set |controller|.\[[started]] to true.
+ 1. Perform ! [$WritableStreamDefaultControllerAdvanceQueueIfNeeded$](|controller|).
+ 1. [=Upon rejection=] of |startPromise| with reason |r|,
+ 1. Assert: |stream|.\[[state]] is "`writable"` or `"erroring`".
+ 1. Set |controller|.\[[started]] to true.
+ 1. Perform ! [$WritableStreamDealWithRejection$](|stream|, |r|).
+
+
+
+ SetUpWritableStreamDefaultControllerFromUnderlyingSink(|stream|,
+ |underlyingSink|, |underlyingSinkDict|, |highWaterMark|, |sizeAlgorithm|) performs the
+ following steps:
+
+ 1. Let |controller| be a [=new=] {{WritableStreamDefaultController}}.
+ 1. Let |startAlgorithm| be an algorithm that returns undefined.
+ 1. Let |writeAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined.
+ 1. Let |closeAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined.
+ 1. Let |abortAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined.
+ 1. If |underlyingSinkDict|["{{UnderlyingSink/start}}"] [=map/exists=], then set |startAlgorithm| to
+ an algorithm which returns the result of [=invoking=]
+ |underlyingSinkDict|["{{UnderlyingSink/start}}"] with argument list « |controller| »
+ and [=callback this value=] |underlyingSink|.
+ 1. If |underlyingSinkDict|["{{UnderlyingSink/write}}"] [=map/exists=], then set |writeAlgorithm| to
+ an algorithm which takes an argument |chunk| and returns the result of [=invoking=]
+ |underlyingSinkDict|["{{UnderlyingSink/write}}"] with argument list « |chunk|,
+ |controller| » and [=callback this value=] |underlyingSink|.
+ 1. If |underlyingSinkDict|["{{UnderlyingSink/close}}"] [=map/exists=], then set |closeAlgorithm| to
+ an algorithm which returns the result of [=invoking=]
+ |underlyingSinkDict|["{{UnderlyingSink/close}}"] with argument list «» and [=callback this
+ value=] |underlyingSink|.
+ 1. If |underlyingSinkDict|["{{UnderlyingSink/abort}}"] [=map/exists=], then set |abortAlgorithm| to
+ an algorithm which takes an argument |reason| and returns the result of [=invoking=]
+ |underlyingSinkDict|["{{UnderlyingSink/abort}}"] with argument list « |reason| » and
+ [=callback this value=] |underlyingSink|.
+ 1. Perform ? [$SetUpWritableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|,
+ |writeAlgorithm|, |closeAlgorithm|, |abortAlgorithm|, |highWaterMark|, |sizeAlgorithm|).
+
+
+
+ WritableStreamDefaultControllerAdvanceQueueIfNeeded(|controller|)
+ performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledWritableStream]].
+ 1. If |controller|.\[[started]] is false, return.
+ 1. If |stream|.\[[inFlightWriteRequest]] is not undefined, return.
+ 1. Let |state| be |stream|.\[[state]].
+ 1. Assert: |state| is not "`closed"` or `"errored`".
+ 1. If |state| is "`erroring`",
+ 1. Perform ! [$WritableStreamFinishErroring$](|stream|).
+ 1. Return.
+ 1. If |controller|.\[[queue]] is empty, return.
+ 1. Let |writeRecord| be ! [$PeekQueueValue$](|controller|).
+ 1. If |writeRecord| is "`close`", perform !
+ [$WritableStreamDefaultControllerProcessClose$](|controller|).
+ 1. Otherwise, perform ! [$WritableStreamDefaultControllerProcessWrite$](|controller|,
+ |writeRecord|.\[[chunk]]).
+
+
+
+ WritableStreamDefaultControllerClearAlgorithms(|controller|)
+ is called once the stream is closed or errored and the algorithms will not be executed any more. By
+ removing the algorithm references it permits the [=underlying sink=] object to be garbage
+ collected even if the {{WritableStream}} itself is still referenced.
+
+ This is observable using weak
+ references. See tc39/proposal-weakrefs#31 for more
+ detail.
+
+ It performs the following steps:
+
+ 1. Set |controller|.\[[writeAlgorithm]] to undefined.
+ 1. Set |controller|.\[[closeAlgorithm]] to undefined.
+ 1. Set |controller|.\[[abortAlgorithm]] to undefined.
+ 1. Set |controller|.\[[strategySizeAlgorithm]] to undefined.
+
+
This algorithm will be performed multiple times in some edge cases. After the first
+ time it will do nothing.
+
+
+
+ WritableStreamDefaultControllerClose(|controller|)
+ performs the following steps:
+
+ 1. Perform ! [$EnqueueValueWithSize$](|controller|, "`close`", 0).
+ 1. Perform ! [$WritableStreamDefaultControllerAdvanceQueueIfNeeded$](|controller|).
+
+
+
+ WritableStreamDefaultControllerError(|controller|,
+ |error|) performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledWritableStream]].
+ 1. Assert: |stream|.\[[state]] is `"writable"`.
+ 1. Perform ! [$WritableStreamDefaultControllerClearAlgorithms$](|controller|).
+ 1. Perform ! [$WritableStreamStartErroring$](|stream|, |error|).
+
+
+
+ WritableStreamDefaultControllerErrorIfNeeded(|controller|,
+ |error|) performs the following steps:
+
+ 1. If |controller|.\[[controlledWritableStream]].\[[state]] is "`writable`", perform !
+ [$WritableStreamDefaultControllerError$](|controller|, |error|).
+
+
+
+ WritableStreamDefaultControllerGetChunkSize(|controller|,
+ |chunk|) performs the following steps:
+
+ 1. Let |returnValue| be the result of performing |controller|.\[[strategySizeAlgorithm]], passing
+ in |chunk|, and interpreting the result as a [=completion record=].
+ 1. If |returnValue| is an abrupt completion,
+ 1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|controller|,
+ |returnValue|.\[[Value]]).
+ 1. Return 1.
+ 1. Return |returnValue|.\[[Value]].
+
+
+
+ WritableStreamDefaultControllerGetBackpressure(|controller|)
+ performs the following steps:
+
+ 1. Let |desiredSize| be ! [$WritableStreamDefaultControllerGetDesiredSize$](|controller|).
+ 1. Return true if |desiredSize| ≤ 0, or false otherwise.
+
+
+
+ WritableStreamDefaultControllerGetDesiredSize(|controller|)
+ performs the following steps:
+
+ 1. Return |controller|.\[[strategyHWM]] − |controller|.\[[queueTotalSize]].
+
+
+
+ WritableStreamDefaultControllerProcessClose(|controller|)
+ performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledWritableStream]].
+ 1. Perform ! [$WritableStreamMarkCloseRequestInFlight$](|stream|).
+ 1. Perform ! [$DequeueValue$](|controller|).
+ 1. Assert: |controller|.\[[queue]] is empty.
+ 1. Let |sinkClosePromise| be the result of performing |controller|.\[[closeAlgorithm]].
+ 1. Perform ! [$WritableStreamDefaultControllerClearAlgorithms$](|controller|).
+ 1. [=Upon fulfillment=] of |sinkClosePromise|,
+ 1. Perform ! [$WritableStreamFinishInFlightClose$](|stream|).
+ 1. [=Upon rejection=] of |sinkClosePromise| with reason |reason|,
+ 1. Perform ! [$WritableStreamFinishInFlightCloseWithError$](|stream|, |reason|).
+
+
+
+ WritableStreamDefaultControllerProcessWrite(|controller|,
+ |chunk|) performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledWritableStream]].
+ 1. Perform ! [$WritableStreamMarkFirstWriteRequestInFlight$](|stream|).
+ 1. Let |sinkWritePromise| be the result of performing |controller|.\[[writeAlgorithm]], passing in
+ |chunk|.
+ 1. [=Upon fulfillment=] of |sinkWritePromise|,
+ 1. Perform ! [$WritableStreamFinishInFlightWrite$](|stream|).
+ 1. Let |state| be |stream|.\[[state]].
+ 1. Assert: |state| is "`writable"` or `"erroring`".
+ 1. Perform ! [$DequeueValue$](|controller|).
+ 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false and |state| is "`writable`",
+ 1. Let |backpressure| be ! [$WritableStreamDefaultControllerGetBackpressure$](|controller|).
+ 1. Perform ! [$WritableStreamUpdateBackpressure$](|stream|, |backpressure|).
+ 1. Perform ! [$WritableStreamDefaultControllerAdvanceQueueIfNeeded$](|controller|).
+ 1. [=Upon rejection=] of |sinkWritePromise| with |reason|,
+ 1. If |stream|.\[[state]] is "`writable`", perform !
+ [$WritableStreamDefaultControllerClearAlgorithms$](|controller|).
+ 1. Perform ! [$WritableStreamFinishInFlightWriteWithError$](|stream|, |reason|).
+
+
+
+ WritableStreamDefaultControllerWrite(|controller|,
+ |chunk|, |chunkSize|) performs the following steps:
+
+ 1. Let |writeRecord| be Record {\[[chunk]]: |chunk|}.
+ 1. Let |enqueueResult| be [$EnqueueValueWithSize$](|controller|, |writeRecord|, |chunkSize|).
+ 1. If |enqueueResult| is an abrupt completion,
+ 1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|controller|,
+ |enqueueResult|.\[[Value]]).
+ 1. Return.
+ 1. Let |stream| be |controller|.\[[controlledWritableStream]].
+ 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false and |stream|.\[[state]] is
+ "`writable`",
+ 1. Let |backpressure| be ! [$WritableStreamDefaultControllerGetBackpressure$](|controller|).
+ 1. Perform ! [$WritableStreamUpdateBackpressure$](|stream|, |backpressure|).
+ 1. Perform ! [$WritableStreamDefaultControllerAdvanceQueueIfNeeded$](|controller|).
+
Transform streams
Using transform streams
- The natural way to use a transform stream is to place it in a pipe between a readable stream
- and a writable stream. Chunks that travel from the readable stream to the writable stream
- will be transformed as they pass through the transform stream. Backpressure is respected, so data will not be
- read faster than it can be transformed and consumed.
+ The natural way to use a transform stream is to place it in a [=piping|pipe=] between a [=readable
+ stream=] and a [=writable stream=]. [=Chunks=] that travel from the [=readable stream=] to the
+ [=writable stream=] will be transformed as they pass through the transform stream.
+ [=Backpressure=] is respected, so data will not be read faster than it can be transformed and
+ consumed.
+
+
+ readableStream
+ .pipeThrough(transformStream)
+ .pipeTo(writableStream)
+ .then(() => console.log("All data successfully transformed!"))
+ .catch(e => console.error("Something went wrong!", e));
+
+
-
- readableStream
- .pipeThrough(transformStream)
- .pipeTo(writableStream)
- .then(() => console.log("All data successfully transformed!"))
- .catch(e => console.error("Something went wrong!", e));
-
+
+ You can also use the {{TransformStream/readable}} and {{TransformStream/writable}} properties of a
+ transform stream directly to access the usual interfaces of a [=readable stream=] and [=writable
+ stream=]. In this example we supply data to the [=writable side=] of the stream using its
+ [=writer=] interface. The [=readable side=] is then piped to
+ anotherWritableStream
.
+
+
+ const writer = transformStream.writable.getWriter();
+ writer.write("input chunk");
+ transformStream.readable.pipeTo(anotherWritableStream);
+
-
- You can also use the {{TransformStream/readable}} and {{TransformStream/writable}} properties of a transform stream
- directly to access the usual interfaces of a readable stream and writable stream. In this example we
- supply data to the writable side of the stream using its writer interface. The readable side is
- then piped to anotherWritableStream
.
+
+ One use of [=identity transform streams=] is to easily convert between readable and writable
+ streams. For example, the {{fetch()}} API accepts a readable stream [=request/body|request body=],
+ but it can be more convenient to write data for uploading via a writable stream interface. Using
+ an identity transform stream addresses this:
+
+
+ const { writable, readable } = new TransformStream();
+ fetch("...", { body: readable }).then(response => /* ... */);
+
+ const writer = writable.getWriter();
+ writer.write(new Uint8Array([0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x73, 0x21]));
+ writer.close();
+
+
+ Another use of identity transform streams is to add additional buffering to a [=pipe=]. In this
+ example we add extra buffering between readableStream
and
+ writableStream
.
+
+
+ const writableStrategy = new ByteLengthQueuingStrategy({ highWaterMark: 1024 * 1024 });
+
+ readableStream
+ .pipeThrough(new TransformStream(undefined, writableStrategy))
+ .pipeTo(writableStream);
+
+
+
+The {{TransformStream}} class
+
+The {{TransformStream}} class is a concrete instance of the general [=transform stream=] concept.
+
+Interface definition
+
+The Web IDL definition for the {{TransformStream}} class is given as follows:
+
+
+[Exposed=(Window,Worker,Worklet)]
+interface TransformStream {
+ constructor(object transformer,
+ optional QueuingStrategy writableStrategy = {},
+ optional QueuingStrategy readableStrategy = {});
+
+ readonly attribute ReadableStream readable;
+ readonly attribute WritableStream writable;
+};
+
+
+Internal slots
+
+Instances of {{TransformStream}} are created with the internal slots described in the following
+table:
+
+
+
+
+ Internal Slot
+ Description (non-normative)
+
+
+ \[[backpressure]]
+ Whether there was backpressure on \[[readable]] the last time it was
+ observed
+
+ \[[backpressureChangePromise]]
+ A promise which is fulfilled and replaced every time the value of
+ \[[backpressure]] changes
+
+ \[[readable]]
+ The {{ReadableStream}} instance controlled by this object
+
+ \[[transformStreamController]]
+ A {{TransformStreamDefaultController}} created with the ability to
+ control \[[readable]] and \[[writable]]
+
+ \[[writable]]
+ The {{WritableStream}} instance controlled by this object
+
+
+The transformer API
+
+The {{TransformStream()}} constructor accepts as its first argument a JavaScript object representing
+the [=transformer=]. Such objects can contain any of the following methods:
+
+
+dictionary Transformer {
+ TransformStreamControllerCallback start;
+ TransformStreamTransformCallback transform;
+ TransformStreamControllerCallback flush;
+ any readableType;
+ any writableType;
+};
-
- const writer = transformStream.writable.getWriter();
- writer.write("input chunk");
- transformStream.readable.pipeTo(anotherWritableStream);
-
-
+callback TransformStreamControllerCallback = Promise (TransformStreamDefaultController controller);
+callback TransformStreamTransformCallback = Promise (TransformStreamDefaultController controller, optional any chunk);
+
-
- One use of identity transform streams is to easily convert between readable and writable streams. For example,
- the {{fetch()}} API accepts a readable stream request body, but it can be more
- convenient to write data for uploading via a writable stream interface. Using an identity transform stream addresses
- this:
+
+ - start(controller)
+ -
+
A function that is called immediately during creation of the {{TransformStream}}.
+
+
Typically this is used to enqueue prefix [=chunks=], using
+ {{TransformStreamDefaultController/enqueue()|controller.enqueue()}}. Those chunks will be read
+ from the [=readable side=] but don't depend on any writes to the [=writable side=].
+
+
If this initial process is asynchronous, for example because it takes some effort to acquire
+ the prefix chunks, the function can return a promise to signal success or failure; a rejected
+ promise will error the stream. Any thrown exceptions will be re-thrown by the
+ {{TransformStream()}} constructor.
+
+
- transform(chunk, controller)
+ -
+
A function called when a new [=chunk=] originally written to the [=writable side=] is ready to
+ be transformed. The stream implementation guarantees that this function will be called only after
+ previous transforms have succeeded, and never before {{Transformer/start|start()}} has completed
+ or after {{Transformer/flush|flush()}} has been called.
+
+
This function performs the actual transformation work of the transform stream. It can enqueue
+ the results using {{TransformStreamDefaultController/enqueue()|controller.enqueue()}}. This
+ permits a single chunk written to the writable side to result in zero or multiple chunks on the
+ [=readable side=], depending on how many times
+ {{TransformStreamDefaultController/enqueue()|controller.enqueue()}} is called.
+ [[#example-ts-lipfuzz]] demonstrates this by sometimes enqueuing zero chunks.
+
+
If the process of transforming is asynchronous, this function can return a promise to signal
+ success or failure of the transformation. A rejected promise will error both the readable and
+ writable sides of the transform stream.
+
+
If no {{Transformer/transform|transform()}} method is supplied, the identity transform is
+ used, which enqueues chunks unchanged from the writable side to the readable side.
+
+
- flush(controller)
+ -
+
A function called after all [=chunks=] written to the [=writable side=] have been transformed
+ by successfully passing through {{Transformer/transform|transform()}}, and the writable side is
+ about to be closed.
+
+
Typically this is used to enqueue suffix chunks to the [=readable side=], before that too
+ becomes closed. An example can be seen in [[#example-ts-lipfuzz]].
+
+
If the flushing process is asynchronous, the function can return a promise to signal success
+ or failure; the result will be communicated to the caller of
+ {{WritableStreamDefaultWriter/write()|stream.writable.write()}}. Additionally, a rejected
+ promise will error both the readable and writable sides of the stream. Throwing an exception is
+ treated the same as returning a rejected promise.
+
+
(Note that there is no need to call
+ {{TransformStreamDefaultController/terminate()|controller.terminate()}} inside
+ {{Transformer/flush|flush()}}; the stream is already in the process of successfully closing down,
+ and terminating it would be counterproductive.)
+
+
- readableType
+ -
+
This property is reserved for future use, so any attempts to supply a value will throw an
+ exception.
-
- const { writable, readable } = new TransformStream();
- fetch("...", { body: readable }).then(response => /* ... */);
+ - writableType
+ -
+
This property is reserved for future use, so any attempts to supply a value will throw an
+ exception.
+
- const writer = writable.getWriter();
- writer.write(new Uint8Array([0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x73, 0x21]));
- writer.close();
-
+The controller
object passed to {{Transformer/start|start()}},
+{{Transformer/transform|transform()}}, and {{Transformer/flush|flush()}} is an instance of
+{{TransformStreamDefaultController}}, and has the ability to enqueue [=chunks=] to the [=readable
+side=], or to terminate or error the stream.
+
+Constructor and properties
+
+
+ stream = new {{TransformStream/constructor(transformer, writableStrategy, readableStrategy)|TransformStream}}(transformer[, writableStrategy[, readableStrategy]])
+ -
+
Creates a new {{TransformStream}} wrapping the provided [=transformer=]. See
+ [[#transformer-api]] for more details on the transformer argument.
+
+
The writableStrategy and readableStrategy arguments are
+ the [=queuing strategy=] objects for the [=writable side|writable=] and [=readable
+ side|readable=] sides respectively. These are used in the construction of the {{WritableStream}}
+ and {{ReadableStream}} objects and can be used to add buffering to a {{TransformStream}}, in
+ order to smooth out variations in the speed of the transformation, or to increase the amount of
+ buffering in a [=pipe=]. If they are not provided, the default behavior will be the same as a
+ {{CountQueuingStrategy}}, with respective [=high water marks=] of 1 and 0.
+
+
readable = stream.{{TransformStream/readable}}
+ -
+
Returns a {{ReadableStream}} representing the [=readable side=] of this transform stream.
+
+
writable = stream.{{TransformStream/writable}}
+ -
+
Returns a {{WritableStream}} representing the [=writable side=] of this transform stream.
+
- Another use of identity transform streams is to add additional buffering to a pipe. In this example we add
- extra buffering between readableStream
and writableStream
.
+
+ The TransformStream(|transformer|,
+ |writableStrategy|, |readableStrategy|) constructor steps are:
+
+ 1. Let |transformerDict| be |transformer|, [=converted to an IDL value=] of type {{Transformer}}.
+ We cannot declare the |transformer| argument as having the {{Transformer}} type
+ directly, because doing so would lose the reference to the original object. We need to retain
+ the object so we can [=invoke=] the various methods on it.
+ 1. If |transformerDict|["{{Transformer/readableType}}"] [=map/exists=], throw a {{RangeError}}
+ exception.
+ 1. If |transformerDict|["{{Transformer/writableType}}"] [=map/exists=], throw a {{RangeError}}
+ exception.
+ 1. Let |writableSizeAlgorithm| be ? [$ExtractSizeAlgorithm$](|writableStrategy|).
+ 1. Let |writableHighWaterMark| be ? [$ExtractHighWaterMark$](|writableStrategy|, 1).
+ 1. Let |readableSizeAlgorithm| be ? [$ExtractSizeAlgorithm$](|readableStrategy|).
+ 1. Let |readableHighWaterMark| be ? [$ExtractHighWaterMark$](|readableStrategy|, 0).
+ 1. Let |startPromise| be [=a new promise=].
+ 1. Perform ! [$InitializeTransformStream$]([=this=], |startPromise|, |writableHighWaterMark|,
+ |writableSizeAlgorithm|, |readableHighWaterMark|, |readableSizeAlgorithm|).
+ 1. Perform ? [$SetUpTransformStreamDefaultControllerFromTransformer$]([=this=], |transformer|,
+ |transformerDict|).
+ 1. Let |startPromise| be [=a new promise=].
+ 1. If |transformerDict|["{{Transformer/start}}"] [=map/exists=], then [=resolve=] |startPromise|
+ with the result of [=invoking=] |transformerDict|["{{Transformer/start}}"] with argument list
+ « [=this=].\[[transformStreamController]] » and [=callback this value=] |transformer|.
+ 1. Otherwise, [=resolve=] |startPromise| with undefined.
+
-
- const writableStrategy = new ByteLengthQueuingStrategy({ highWaterMark: 1024 * 1024 });
+
+ The readable attribute's getter steps
+ are:
- readableStream
- .pipeThrough(new TransformStream(undefined, writableStrategy))
- .pipeTo(writableStream);
-
+ 1. Return [=this=].\[[readable]].
-Class TransformStream
+
+ The writable attribute's getter steps
+ are:
-Class definition
+ 1. Return [=this=].\[[writable]].
+
-
+The {{TransformStreamDefaultController}} class
-This section is non-normative.
+The {{TransformStreamDefaultController}} class has methods that allow manipulation of the
+associated {{ReadableStream}} and {{WritableStream}}. When constructing a {{TransformStream}}, the
+[=transformer=] object is given a corresponding {{TransformStreamDefaultController}} instance to
+manipulate.
-If one were to write the {{TransformStream}} class in something close to the syntax of [[!ECMASCRIPT]], it would look
-like
+Interface definition
-
- class TransformStream {
- constructor(transformer = {}, writableStrategy = {}, readableStrategy = {})
+The Web IDL definition for the {{TransformStreamDefaultController}} class is given as follows:
- get readable()
- get writable()
- }
-
+
+[Exposed=(Window,Worker,Worklet)]
+interface TransformStreamDefaultController {
+ readonly attribute unrestricted double? desiredSize;
-
+ void enqueue(optional any chunk);
+ void error(optional any reason);
+ void terminate();
+};
+
-Internal slots
+Internal slots
-Instances of {{TransformStream}} are created with the internal slots described in the following table:
+Instances of {{TransformStreamDefaultController}} are created with the internal slots described in
+the following table:
-
-
- Internal Slot
- Description (non-normative)
-
-
+
- \[[backpressure]]
- Whether there was backpressure on \[[readable]] the last time it was observed
-
+ Internal Slot
+ Description (non-normative)
+
- \[[backpressureChangePromise]]
- A promise which is fulfilled and replaced every time the value of \[[backpressure]]
- changes
-
+ \[[controlledTransformStream]]
+ The {{TransformStream}} instance controlled
- \[[readable]]
- The {{ReadableStream}} instance controlled by this object
-
+ \[[flushAlgorithm]]
+ A promise-returning algorithm which communicates a requested close to
+ the [=transformer=]
- \[[transformStreamController]]
- A {{TransformStreamDefaultController}} created with the ability to control \[[readable]]
- and \[[writable]]; also used for the IsTransformStream brand check
-
-
- \[[writable]]
- The {{WritableStream}} instance controlled by this object
-
+ \[[transformAlgorithm]]
+ A promise-returning algorithm, taking one argument (the [=chunk=] to
+ transform), which requests the [=transformer=] perform its transformation
-new TransformStream(transformer = {}, writableStrategy = {},
-readableStrategy = {})
+Methods and properties
+
+
+ desiredSize = controller.{{TransformStreamDefaultController/desiredSize}}
+ -
+
Returns the [=desired size to fill a stream's internal queue|desired size to fill the
+ readable side's internal queue=]. It can be negative, if the queue is over-full.
+
+
controller.{{TransformStreamDefaultController/enqueue()|enqueue}}(chunk)
+ -
+
Enqueues the given [=chunk=] chunk in the [=readable side=] of the controlled
+ transform stream.
+
+
controller.{{TransformStreamDefaultController/error()|error}}(e)
+ -
+
Errors the both the [=readable side=] and the [=writable side=] of the controlled transform
+ stream, making all future interactions with it fail with the given error e. Any
+ [=chunks=] queued for transformation will be discarded.
+
+
controller.{{TransformStreamDefaultController/terminate()|terminate}}()
+ -
+
Closes the [=readable side=] and errors the [=writable side=] of the controlled transform
+ stream. This is useful when the [=transformer=] only needs to consume a portion of the [=chunks=]
+ written to the [=writable side=].
+
-
- The transformer
argument represents the transformer, as described in [[#transformer-api]].
-
- The writableStrategy
and readableStrategy
arguments are the queuing strategy objects
- for the writable and readable sides respectively. These are used in the construction of the {{WritableStream}} and
- {{ReadableStream}} objects and can be used to add buffering to a {{TransformStream}}, in order to smooth out
- variations in the speed of the transformation, or to increase the amount of buffering in a pipe. If they are
- not provided, the default behavior will be the same as a {{CountQueuingStrategy}}, with respective high water
- marks of 1 and 0.
-
-
-
- 1. Let _writableSizeFunction_ be ? GetV(_writableStrategy_, `"size"`).
- 1. Let _writableHighWaterMark_ be ? GetV(_writableStrategy_, `"highWaterMark"`).
- 1. Let _readableSizeFunction_ be ? GetV(_readableStrategy_, `"size"`).
- 1. Let _readableHighWaterMark_ be ? GetV(_readableStrategy_, `"highWaterMark"`).
- 1. Let _writableType_ be ? GetV(_transformer_, `"writableType"`).
- 1. If _writableType_ is not *undefined*, throw a *RangeError* exception.
- 1. Let _writableSizeAlgorithm_ be ? MakeSizeAlgorithmFromSizeFunction(_writableSizeFunction_).
- 1. If _writableHighWaterMark_ is *undefined*, set _writableHighWaterMark_ to *1*.
- 1. Set _writableHighWaterMark_ to ? ValidateAndNormalizeHighWaterMark(_writableHighWaterMark_).
- 1. Let _readableType_ be ? GetV(_transformer_, `"readableType"`).
- 1. If _readableType_ is not *undefined*, throw a *RangeError* exception.
- 1. Let _readableSizeAlgorithm_ be ? MakeSizeAlgorithmFromSizeFunction(_readableSizeFunction_).
- 1. If _readableHighWaterMark_ is *undefined*, set _readableHighWaterMark_ to *0*.
- 1. Set _readableHighWaterMark_ be ? ValidateAndNormalizeHighWaterMark(_readableHighWaterMark_).
- 1. Let _startPromise_ be a new promise.
- 1. Perform ! InitializeTransformStream(*this*, _startPromise_, _writableHighWaterMark_, _writableSizeAlgorithm_,
- _readableHighWaterMark_, _readableSizeAlgorithm_).
- 1. Perform ? SetUpTransformStreamDefaultControllerFromTransformer(*this*, _transformer_).
- 1. Let _startResult_ be ? InvokeOrNoop(_transformer_, `"start"`, « *this*.[[transformStreamController]] »).
- 1. Resolve _startPromise_ with _startResult_.
-
-
-Transformer API
+
+ The desiredSize attribute's getter steps are:
-
+ 1. Let |readableController| be
+ [=this=].\[[controlledTransformStream]].\[[readable]].\[[readableStreamController]].
+ 1. Return ! [$ReadableStreamDefaultControllerGetDesiredSize$](|readableController|).
+
-This section is non-normative.
+
+ The enqueue(|chunk|) method steps are:
-The {{TransformStream()}} constructor accepts as its first argument a JavaScript object representing the
-transformer. Such objects can contain any of the following methods:
+ 1. Perform ? [$TransformStreamDefaultControllerEnqueue$]([=this=], |chunk|).
+
-
- - start(controller)
- -
-
A function that is called immediately during creation of the {{TransformStream}}.
+
+ The error(|e|) method steps are:
- Typically this is used to enqueue prefix chunks, using
- {{TransformStreamDefaultController/enqueue()|controller.enqueue()}}. Those chunks will be read from the readable
- side but don't depend on any writes to the writable side.
+ 1. Perform ? [$TransformStreamDefaultControllerError$]([=this=], |e|).
+
- If this initial process is asynchronous, for example because it takes some effort to acquire the prefix
- chunks, the function can return a promise to signal success or failure; a rejected promise will error the stream.
- Any thrown exceptions will be re-thrown by the {{TransformStream()}} constructor.
-
+
+ The terminate() method steps are:
- - transform(chunk, controller)
- -
-
A function called when a new chunk originally written to the writable side is ready to be
- transformed. The stream implementation guarantees that this function will be called only after previous transforms
- have succeeded, and never before {{underlying sink/start()}} has completed or after {{transformer/flush()}} has been
- called.
-
- This function performs the actual transformation work of the transform stream. It can enqueue the results using
- {{TransformStreamDefaultController/enqueue()|controller.enqueue()}}. This permits a single chunk written to the
- writable side to result in zero or multiple chunks on the readable side, depending on how many times
- {{TransformStreamDefaultController/enqueue()|controller.enqueue()}} is called. [[#example-ts-lipfuzz]] demonstrates
- this by sometimes enqueuing zero chunks.
-
- If the process of transforming is asynchronous, this function can return a promise to signal success or failure
- of the transformation. A rejected promise will error both the readable and writable sides of the transform
- stream.
-
- If no {{transformer/transform()}} is supplied, the identity transform is used, which enqueues chunks unchanged
- from the writable side to the readable side.
-
-
- - flush(controller)
- -
-
A function called after all chunks written to the writable side have been transformed by
- successfully passing through {{transformer/transform()}}, and the writable side is about to be closed.
+ 1. Perform ? [$TransformStreamDefaultControllerTerminate$]([=this=]).
+
- Typically this is used to enqueue suffix chunks to the readable side, before that too becomes closed. An
- example can be seen in [[#example-ts-lipfuzz]].
+Abstract operations
+
+Working with transform streams
+
+The following abstract operations operate on {{TransformStream}} instances at a higher level. Some
+are even meant to be generally useful by other specifications.
+
+
+ CreateTransformStream(|startAlgorithm|, |transformAlgorithm|, |flushAlgorithm|[,
+ |writableHighWaterMark|[, |writableSizeAlgorithm|[, |readableHighWaterMark|[,
+ |readableSizeAlgorithm|]]]]) is meant to be called from other specifications that wish to
+ create {{TransformStream}} instances. The |transformAlgorithm| and |flushAlgorithm| algorithms
+ must return promises; if supplied, |writableHighWaterMark| and |readableHighWaterMark| must be
+ non-negative, non-NaN numbers; and if supplied, |writableSizeAlgorithm| and
+ |readableSizeAlgorithm| must be algorithms accepting [=chunk=] objects and returning numbers.
+
+ It performs the following steps:
+
+ 1. If |writableHighWaterMark| was not passed, set it to 1.
+ 1. If |writableSizeAlgorithm| was not passed, set it to an algorithm that returns 1.
+ 1. If |readableHighWaterMark| was not passed, set it to 0.
+ 1. If |readableSizeAlgorithm| was not passed, set it to an algorithm that returns 1.
+ 1. Assert: ! [$IsNonNegativeNumber$](|writableHighWaterMark|) is true.
+ 1. Assert: ! [$IsNonNegativeNumber$](|readableHighWaterMark|) is true.
+ 1. Let |stream| be a [=new=] {{TransformStream}}.
+ 1. Let |startPromise| be [=a new promise=].
+ 1. Perform ! [$InitializeTransformStream$](|stream|, |startPromise|, |writableHighWaterMark|,
+ |writableSizeAlgorithm|, |readableHighWaterMark|, |readableSizeAlgorithm|).
+ 1. Let |controller| be a [=new=] {{TransformStreamDefaultController}}.
+ 1. Perform ! [$SetUpTransformStreamDefaultController$](|stream|, |controller|,
+ |transformAlgorithm|, |flushAlgorithm|).
+ 1. Let |startResult| be the result of performing |startAlgorithm|. (This may throw an exception.)
+ 1. [=Resolve=] |startPromise| with |startResult|.
+ 1. Return |stream|.
+
+ This abstract operation will throw an exception if and only if the supplied
+ |startAlgorithm| throws.
+
- If the flushing process is asynchronous, the function can return a promise to signal success or failure; the
- result will be communicated to the caller of {{WritableStreamDefaultWriter/write()|stream.writable.write()}}.
- Additionally, a rejected promise will error both the readable and writable sides of the stream. Throwing an
- exception is treated the same as returning a rejected promise.
+
+ InitializeTransformStream(|stream|, |startPromise|,
+ |writableHighWaterMark|, |writableSizeAlgorithm|, |readableHighWaterMark|,
+ |readableSizeAlgorithm|) performs the following steps:
+
+ 1. Let |startAlgorithm| be an algorithm that returns |startPromise|.
+ 1. Let |writeAlgorithm| be the following steps, taking a |chunk| argument:
+ 1. Return ! [$TransformStreamDefaultSinkWriteAlgorithm$](|stream|, |chunk|).
+ 1. Let |abortAlgorithm| be the following steps, taking a |reason| argument:
+ 1. Return ! [$TransformStreamDefaultSinkAbortAlgorithm$](|stream|, |reason|).
+ 1. Let |closeAlgorithm| be the following steps:
+ 1. Return ! [$TransformStreamDefaultSinkCloseAlgorithm$](|stream|).
+ 1. Set |stream|.\[[writable]] to ! [$CreateWritableStream$](|startAlgorithm|, |writeAlgorithm|,
+ |closeAlgorithm|, |abortAlgorithm|, |writableHighWaterMark|, |writableSizeAlgorithm|).
+ 1. Let |pullAlgorithm| be the following steps:
+ 1. Return ! [$TransformStreamDefaultSourcePullAlgorithm$](|stream|).
+ 1. Let |cancelAlgorithm| be the following steps, taking a |reason| argument:
+ 1. Perform ! [$TransformStreamErrorWritableAndUnblockWrite$](|stream|, |reason|).
+ 1. Return [=a promise resolved with=] undefined.
+ 1. Set |stream|.\[[readable]] to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|,
+ |cancelAlgorithm|, |readableHighWaterMark|, |readableSizeAlgorithm|).
+ 1. Set |stream|.\[[backpressure]] and |stream|.\[[backpressureChangePromise]] to undefined.
+ The \[[backpressure]] slot is set to undefined so that it can be initialized by
+ [$TransformStreamSetBackpressure$]. Alternatively, implementations can use a strictly boolean
+ value for \[[backpressure]] and change the way it is initialized. This will not be visible to
+ user code so long as the initialization is correctly completed before the transformer's
+ {{Transformer/start|start()}} method is called.
+ 1. Perform ! [$TransformStreamSetBackpressure$](|stream|, true).
+ 1. Set |stream|.\[[transformStreamController]] to undefined.
+
- (Note that there is no need to call {{TransformStreamDefaultController/terminate()|controller.terminate()}}
- inside {{transformer/flush()}}; the stream is already in the process of successfully closing down, and terminating
- it would be counterproductive.)
-
-
+
+ TransformStreamError(|stream|, |e|) performs the following steps:
-The controller
object passed to {{transformer/start()}}, {{transformer/transform()}}, and
-{{transformer/flush()}} is an instance of {{TransformStreamDefaultController}}, and has the ability to enqueue
-chunks to the readable side, or to terminate or error the stream.
+ 1. Perform !
+ [$ReadableStreamDefaultControllerError$](|stream|.\[[readable]].\[[readableStreamController]],
+ |e|).
+ 1. Perform ! [$TransformStreamErrorWritableAndUnblockWrite$](|stream|, |e|).
+ This operation works correctly when one or both sides are already errored. As a
+ result, calling algorithms do not need to check stream states when responding to an error
+ condition.
-Properties of the {{TransformStream}} prototype
+
+ TransformStreamErrorWritableAndUnblockWrite(|stream|,
+ |e|) performs the following steps:
+
+ 1. Perform !
+ [$TransformStreamDefaultControllerClearAlgorithms$](|stream|.\[[transformStreamController]]).
+ 1. Perform !
+ [$WritableStreamDefaultControllerErrorIfNeeded$](|stream|.\[[writable]].\[[writableStreamController]],
+ |e|).
+ 1. If |stream|.\[[backpressure]] is true, perform ! [$TransformStreamSetBackpressure$](|stream|,
+ false).
+
+ The [$TransformStreamDefaultSinkWriteAlgorithm$] abstract operation could be
+ waiting for the promise stored in the \[[backpressureChangePromise]] slot to resolve. The call to
+ [$TransformStreamSetBackpressure$] ensures that the promise always resolves.
+
-get readable
+
+ TransformStreamSetBackpressure(|stream|,
+ |backpressure|) performs the following steps:
-
- The readable
getter gives access to the readable side of the transform stream.
+ 1. Assert: |stream|.\[[backpressure]] is not |backpressure|.
+ 1. If |stream|.\[[backpressureChangePromise]] is not undefined, [=resolve=]
+ stream.\[[backpressureChangePromise]] with undefined.
+ 1. Set |stream|.\[[backpressureChangePromise]] to [=a new promise=].
+ 1. Set |stream|.\[[backpressure]] to |backpressure|.
-
- 1. If ! IsTransformStream(*this*) is *false*, throw a *TypeError* exception.
- 1. Return *this*.[[readable]].
-
+Default controllers
-get writable
+The following abstract operations support the implementaiton of the
+{{TransformStreamDefaultController}} class.
-
- The writable
getter gives access to the writable side of the transform stream.
-
-
-
- 1. If ! IsTransformStream(*this*) is *false*, throw a *TypeError* exception.
- 1. Return *this*.[[writable]].
-
-
-General transform stream abstract operations
-
-CreateTransformStream (
-startAlgorithm, transformAlgorithm, flushAlgorithm
-[, writableHighWaterMark [, writableSizeAlgorithm [, readableHighWaterMark [,
-readableSizeAlgorithm ] ] ] ] )
-
-This abstract operation is meant to be called from other specifications that wish to create {{TransformStream}}
-instances. The transformAlgorithm and flushAlgorithm algorithms must return promises; if supplied,
-writableHighWaterMark and readableHighWaterMark must be non-negative, non-NaN numbers; and if
-supplied, writableSizeAlgorithm and readableSizeAlgorithm must be algorithms accepting
-chunk objects and returning numbers.
-
-CreateTransformStream throws an exception if and only if the supplied startAlgorithm
-throws.
-
-
- 1. If _writableHighWaterMark_ was not passed, set it to *1*.
- 1. If _writableSizeAlgorithm_ was not passed, set it to an algorithm that returns *1*.
- 1. If _readableHighWaterMark_ was not passed, set it to *0*.
- 1. If _readableSizeAlgorithm_ was not passed, set it to an algorithm that returns *1*.
- 1. Assert: ! IsNonNegativeNumber(_writableHighWaterMark_) is *true*.
- 1. Assert: ! IsNonNegativeNumber(_readableHighWaterMark_) is *true*.
- 1. Let _stream_ be ObjectCreate(the original value of `TransformStream`'s `prototype` property).
- 1. Let _startPromise_ be a new promise.
- 1. Perform ! InitializeTransformStream(_stream_, _startPromise_, _writableHighWaterMark_, _writableSizeAlgorithm_,
- _readableHighWaterMark_, _readableSizeAlgorithm_).
- 1. Let _controller_ be ObjectCreate(the original value of `TransformStreamDefaultController`'s `prototype`
- property).
- 1. Perform ! SetUpTransformStreamDefaultController(_stream_, _controller_, _transformAlgorithm_, _flushAlgorithm_).
- 1. Let _startResult_ be the result of performing _startAlgorithm_. (This may throw an exception.)
- 1. Resolve _startPromise_ with _startResult_.
- 1. Return _stream_.
-
-
-InitializeTransformStream (
-stream, startPromise, writableHighWaterMark, writableSizeAlgorithm,
-readableHighWaterMark, readableSizeAlgorithm )
-
-
- 1. Let _startAlgorithm_ be an algorithm that returns _startPromise_.
- 1. Let _writeAlgorithm_ be the following steps, taking a _chunk_ argument:
- 1. Return ! TransformStreamDefaultSinkWriteAlgorithm(_stream_, _chunk_).
- 1. Let _abortAlgorithm_ be the following steps, taking a _reason_ argument:
- 1. Return ! TransformStreamDefaultSinkAbortAlgorithm(_stream_, _reason_).
- 1. Let _closeAlgorithm_ be the following steps:
- 1. Return ! TransformStreamDefaultSinkCloseAlgorithm(_stream_).
- 1. Set _stream_.[[writable]] to ! CreateWritableStream(_startAlgorithm_, _writeAlgorithm_, _closeAlgorithm_,
- _abortAlgorithm_, _writableHighWaterMark_, _writableSizeAlgorithm_).
- 1. Let _pullAlgorithm_ be the following steps:
- 1. Return ! TransformStreamDefaultSourcePullAlgorithm(_stream_).
- 1. Let _cancelAlgorithm_ be the following steps, taking a _reason_ argument:
- 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(_stream_, _reason_).
- 1. Return a promise resolved with *undefined*.
- 1. Set _stream_.[[readable]] to ! CreateReadableStream(_startAlgorithm_, _pullAlgorithm_, _cancelAlgorithm_,
- _readableHighWaterMark_, _readableSizeAlgorithm_).
- 1. Set _stream_.[[backpressure]] and _stream_.[[backpressureChangePromise]] to *undefined*.
- The [[backpressure]] slot is set to *undefined* so that it can be initialized by
- TransformStreamSetBackpressure. Alternatively, implementations can use a strictly boolean value for
- [[backpressure]] and change the way it is initialized. This will not be visible to user code so long as the
- initialization is correctly completed before _transformer_'s {{transformer/start()}} method is called.
- 1. Perform ! TransformStreamSetBackpressure(_stream_, *true*).
- 1. Set _stream_.[[transformStreamController]] to *undefined*.
-
-
-IsTransformStream ( x )
-
-
- 1. If Type(_x_) is not Object, return *false*.
- 1. If _x_ does not have a [[transformStreamController]] internal slot, return *false*.
- 1. Return *true*.
-
-
-TransformStreamError ( stream,
-e )
-
-
- 1. Perform ! ReadableStreamDefaultControllerError(_stream_.[[readable]].[[readableStreamController]], _e_).
- 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(_stream_, _e_).
-
-
-This operation works correctly when one or both sides are already errored. As a result, calling
-algorithms do not need to check stream states when responding to an error condition.
-
-TransformStreamErrorWritableAndUnblockWrite ( stream, e )
-
-
- 1. Perform ! TransformStreamDefaultControllerClearAlgorithms(_stream_.[[transformStreamController]]).
- 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(_stream_.[[writable]].[[writableStreamController]], _e_).
- 1. If _stream_.[[backpressure]] is *true*, perform ! TransformStreamSetBackpressure(_stream_, *false*).
-
-
-The TransformStreamDefaultSinkWriteAlgorithm abstract operation could be waiting for the promise
-stored in the \[[backpressureChangePromise]] slot to resolve. This call to TransformStreamSetBackpressure ensures that
-the promise always resolves.
-
-TransformStreamSetBackpressure
-( stream, backpressure )
-
-
- 1. Assert: _stream_.[[backpressure]] is not _backpressure_.
- 1. If _stream_.[[backpressureChangePromise]] is not *undefined*, resolve
- stream.[[backpressureChangePromise]] with *undefined*.
- 1. Set _stream_.[[backpressureChangePromise]] to a new promise.
- 1. Set _stream_.[[backpressure]] to _backpressure_.
-
-
-Class
-TransformStreamDefaultController
-
-The {{TransformStreamDefaultController}} class has methods that allow manipulation of the associated {{ReadableStream}}
-and {{WritableStream}}. When constructing a {{TransformStream}}, the transformer object is given a corresponding
-{{TransformStreamDefaultController}} instance to manipulate.
-
-Class definition
-
-
+
+ SetUpTransformStreamDefaultController(|stream|,
+ |controller|, |transformAlgorithm|, |flushAlgorithm|) performs the following steps:
-This section is non-normative.
+ 1. Assert: |stream| [=implements=] {{TransformStream}}.
+ 1. Assert: |stream|.\[[transformStreamController]] is undefined.
+ 1. Set |controller|.\[[controlledTransformStream]] to |stream|.
+ 1. Set |stream|.\[[transformStreamController]] to |controller|.
+ 1. Set |controller|.\[[transformAlgorithm]] to |transformAlgorithm|.
+ 1. Set |controller|.\[[flushAlgorithm]] to |flushAlgorithm|.
+
-If one were to write the {{TransformStreamDefaultController}} class in something close to the syntax of [[!ECMASCRIPT]],
-it would look like
+
+ SetUpTransformStreamDefaultControllerFromTransformer(|stream|,
+ |transformer|, |transformerDict|) performs the following steps:
+
+ 1. Let |controller| be a [=new=] {{TransformStreamDefaultController}}.
+ 1. Let |transformAlgorithm| be the following steps, taking a |chunk| argument:
+ 1. Let |result| be [$TransformStreamDefaultControllerEnqueue$](|controller|, |chunk|).
+ 1. If |result| is an abrupt completion, return [=a promise rejected with=] |result|.\[[Value]].
+ 1. Otherwise, return [=a promise resolved with=] undefined.
+ 1. Let |flushAlgorithm| be an algorithm which returns [=a promise resolved with=] undefined.
+ 1. If |transformerDict|["{{Transformer/transform}}"] [=map/exists=], set |transformAlgorithm| to an
+ algorithm which takes an argument |chunk| and returns the result of [=invoking=]
+ |transformerDict|["{{Transformer/transform}}"] with argument list « |chunk|,
+ |controller| ») and [=callback this value=] |transformer|.
+ 1. If |transformerDict|["{{Transformer/flush}}"] [=map/exists=], set |flushAlgorithm| to an
+ algorithm which returns the result of [=invoking=] |transformerDict|["{{Transformer/flush}}"]
+ with argument list « |controller| » and [=callback this value=] |transformer|.
+ 1. Perform ! [$SetUpTransformStreamDefaultController$](|stream|, |controller|,
+ |transformAlgorithm|, |flushAlgorithm|).
+
-
- class TransformStreamDefaultController {
- constructor() // always throws
+
+ TransformStreamDefaultControllerClearAlgorithms(|controller|)
+ is called once the stream is closed or errored and the algorithms will not be executed any more.
+ By removing the algorithm references it permits the [=transformer=] object to be garbage collected
+ even if the {{TransformStream}} itself is still referenced.
- get desiredSize()
+ This is observable using weak
+ references. See tc39/proposal-weakrefs#31 for more
+ detail.
- enqueue(chunk)
- error(reason)
- terminate()
- }
-
+ It performs the following steps:
+ 1. Set |controller|.\[[transformAlgorithm]] to undefined.
+ 1. Set |controller|.\[[flushAlgorithm]] to undefined.
-Internal slots
-
-Instances of {{TransformStreamDefaultController}} are created with the internal slots described in the following table:
-
-
-
-
- Internal Slot
- Description (non-normative)
-
-
-
- \[[controlledTransformStream]]
- The {{TransformStream}} instance controlled; also used for the
- IsTransformStreamDefaultController brand check
-
-
- \[[flushAlgorithm]]
- A promise-returning algorithm which communicates a requested close to the
- transformer
-
-
- \[[transformAlgorithm]]
- A promise-returning algorithm, taking one argument (the chunk to transform), which
- requests the transformer perform its transformation
-
+
+ TransformStreamDefaultControllerEnqueue(|controller|, |chunk|) is meant to be called
+ by other specifications that wish to enqueue [=chunks=] in the [=readable side=], in the same way
+ a developer would enqueue chunks using the stream's associated controller object. Specifications
+ should not do this to streams or controllers they did not create.
+
+ It performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledTransformStream]].
+ 1. Let |readableController| be |stream|.\[[readable]].\[[readableStreamController]].
+ 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|readableController|) is false, throw
+ a {{TypeError}} exception.
+ 1. Let |enqueueResult| be [$ReadableStreamDefaultControllerEnqueue$](|readableController|,
+ |chunk|).
+ 1. If |enqueueResult| is an abrupt completion,
+ 1. Perform ! [$TransformStreamErrorWritableAndUnblockWrite$](|stream|,
+ |enqueueResult|.\[[Value]]).
+ 1. Throw |stream|.\[[readable]].\[[storedError]].
+ 1. Let |backpressure| be !
+ [$ReadableStreamDefaultControllerHasBackpressure$](|readableController|).
+ 1. If |backpressure| is not |stream|.\[[backpressure]],
+ 1. Assert: |backpressure| is true.
+ 1. Perform ! [$TransformStreamSetBackpressure$](|stream|, true).
+
+
+ TransformStreamDefaultControllerError(|controller|, |e|) is meant to be called by
+ other specifications that wish to move the transform stream to an errored state, in the same way a
+ developer would error the stream using the stream's associated controller object. Specifications
+ should not do this to streams or controllers they did not create.
-new TransformStreamDefaultController()
+ It performs the following steps:
-
- The TransformStreamDefaultController
constructor cannot be used directly;
- {{TransformStreamDefaultController}} instances are created automatically during {{TransformStream}} construction.
+ 1. Perform ! [$TransformStreamError$](|controller|.\[[controlledTransformStream]], |e|).
-
- 1. Throw a *TypeError* exception.
-
-
-Properties of the {{TransformStreamDefaultController}} prototype
+
+ TransformStreamDefaultControllerPerformTransform(|controller|, |chunk|)
+ performs the following steps:
+
+ 1. Let |transformPromise| be the result of performing
+ |controller|.\[[transformAlgorithm]], passing |chunk|.
+ 1. Return the result of [=reacting=] to |transformPromise| with the following
+ rejection steps given the argument |r|:
+ 1. Perform ! [$TransformStreamError$](|controller|.\[[controlledTransformStream]], |r|).
+ 1. Throw |r|.
+
-get
-desiredSize
+
+ TransformStreamDefaultControllerTerminate(|controller|) is meant to be called by
+ other specifications that wish to terminate the transform stream, in the same way a
+ developer-created stream would be terminated by its associated controller object. Specifications
+ should not do this to streams or controllers they did not create.
+
+ It performs the following steps:
+
+ 1. Let |stream| be |controller|.\[[controlledTransformStream]].
+ 1. Let |readableController| be |stream|.\[[readable]].\[[readableStreamController]].
+ 1. Perform ! [$ReadableStreamDefaultControllerClose$](|readableController|).
+ 1. Let |error| be a {{TypeError}} exception indicating that the stream has been terminated.
+ 1. Perform ! [$TransformStreamErrorWritableAndUnblockWrite$](|stream|, |error|).
+
-
- The desiredSize
getter returns the desired size
- to fill the readable side's internal queue. It can be negative, if the queue is over-full.
+Default sinks
+
+The following abstract operations are used to implement the [=underlying sink=] for the [=writable
+side=] of [=transform streams=].
+
+
+ TransformStreamDefaultSinkWriteAlgorithm(|stream|,
+ |chunk|) performs the following steps:
+
+ 1. Assert: |stream|.\[[writable]].\[[state]] is "`writable`".
+ 1. Let |controller| be |stream|.\[[transformStreamController]].
+ 1. If |stream|.\[[backpressure]] is true,
+ 1. Let |backpressureChangePromise| be |stream|.\[[backpressureChangePromise]].
+ 1. Assert: |backpressureChangePromise| is not undefined.
+ 1. Return the result of [=reacting=] to |backpressureChangePromise| with the following fulfillment
+ steps:
+ 1. Let |writable| be |stream|.\[[writable]].
+ 1. Let |state| be |writable|.\[[state]].
+ 1. If |state| is "`erroring`", throw |writable|.\[[storedError]].
+ 1. Assert: |state| is "`writable`".
+ 1. Return ! [$TransformStreamDefaultControllerPerformTransform$](|controller|, |chunk|).
+ 1. Return ! [$TransformStreamDefaultControllerPerformTransform$](|controller|, |chunk|).
-
- 1. If ! IsTransformStreamDefaultController(*this*) is *false*, throw a *TypeError* exception.
- 1. Let _readableController_ be *this*.[[controlledTransformStream]].[[readable]].[[readableStreamController]].
- 1. Return ! ReadableStreamDefaultControllerGetDesiredSize(_readableController_).
-
+
+ TransformStreamDefaultSinkAbortAlgorithm(|stream|,
+ |reason|) performs the following steps:
-enqueue(chunk)
+ 1. Perform ! [$TransformStreamError$](|stream|, |reason|).
+ 1. Return [=a promise resolved with=] undefined.
+
-
- The enqueue
method will enqueue a given chunk in the readable side.
+
+ TransformStreamDefaultSinkCloseAlgorithm(|stream|)
+ performs the following steps:
+
+ 1. Let |readable| be |stream|.\[[readable]].
+ 1. Let |controller| be |stream|.\[[transformStreamController]].
+ 1. Let |flushPromise| be the result of performing |controller|.\[[flushAlgorithm]].
+ 1. Perform ! [$TransformStreamDefaultControllerClearAlgorithms$](|controller|).
+ 1. Return the result of [=reacting=] to |flushPromise|:
+ 1. If |flushPromise| was fulfilled, then:
+ 1. If |readable|.\[[state]] is "`errored`", throw |readable|.\[[storedError]].
+ 1. Let |readableController| be |readable|.\[[readableStreamController]].
+ 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|readableController|) is true,
+ perform ! [$ReadableStreamDefaultControllerClose$](|readableController|).
+ 1. If |flushPromise| was rejected with reason |r|, then:
+ 1. Perform ! [$TransformStreamError$](|stream|, |r|).
+ 1. Throw |readable|.\[[storedError]].
-
- 1. If ! IsTransformStreamDefaultController(*this*) is *false*, throw a *TypeError* exception.
- 1. Perform ? TransformStreamDefaultControllerEnqueue(*this*, _chunk_).
-
+Default source
-error(reason)
+The following abstract operation is used to implement the [=underlying source=] for the [=readable
+side=] of [=transform streams=].
-
- The error
method will error both the readable side and the writable side of the controlled
- transform stream, making all future interactions fail with the given reason
. Any chunks
- queued for transformation will be discarded.
-
+
+ TransformStreamDefaultSourcePullAlgorithm(|stream|)
+ performs the following steps:
-
- 1. If ! IsTransformStreamDefaultController(*this*) is *false*, throw a *TypeError* exception.
- 1. Perform ! TransformStreamDefaultControllerError(*this*, _reason_).
-
+ 1. Assert: |stream|.\[[backpressure]] is true.
+ 1. Assert: |stream|.\[[backpressureChangePromise]] is not undefined.
+ 1. Perform ! [$TransformStreamSetBackpressure$](|stream|, false).
+ 1. Return |stream|.\[[backpressureChangePromise]].
+
-terminate()
+Queuing strategies
-
- The terminate
method will close the readable side and error the writable side of the
- controlled transform stream. This is useful when the transformer only needs to consume a portion of the
- chunks written to the writable side.
-
-
-
- 1. If ! IsTransformStreamDefaultController(*this*) is *false*, throw a *TypeError* exception.
- 1. Perform ! TransformStreamDefaultControllerTerminate(*this*).
-
-
-Transform stream default controller abstract operations
-
-IsTransformStreamDefaultController ( x )
-
-
- 1. If Type(_x_) is not Object, return *false*.
- 1. If _x_ does not have an [[controlledTransformStream]] internal slot, return *false*.
- 1. Return *true*.
-
-
-SetUpTransformStreamDefaultController ( stream, controller, transformAlgorithm,
-flushAlgorithm )
-
-
- 1. Assert: ! IsTransformStream(_stream_) is *true*.
- 1. Assert: _stream_.[[transformStreamController]] is *undefined*.
- 1. Set _controller_.[[controlledTransformStream]] to _stream_.
- 1. Set _stream_.[[transformStreamController]] to _controller_.
- 1. Set _controller_.[[transformAlgorithm]] to _transformAlgorithm_.
- 1. Set _controller_.[[flushAlgorithm]] to _flushAlgorithm_.
-
-
-SetUpTransformStreamDefaultControllerFromTransformer ( stream, transformer )
-
-
- 1. Assert: _transformer_ is not *undefined*.
- 1. Let _controller_ be ObjectCreate(the original value of `TransformStreamDefaultController`'s `prototype`
- property).
- 1. Let _transformAlgorithm_ be the following steps, taking a _chunk_ argument:
- 1. Let _result_ be TransformStreamDefaultControllerEnqueue(_controller_, _chunk_).
- 1. If _result_ is an abrupt completion, return a promise rejected with _result_.[[Value]].
- 1. Otherwise, return a promise resolved with *undefined*.
- 1. Let _transformMethod_ be ? GetV(_transformer_, `"transform"`).
- 1. If _transformMethod_ is not *undefined*,
- 1. If ! IsCallable(_transformMethod_) is *false*, throw a *TypeError* exception.
- 1. Set _transformAlgorithm_ to the following steps, taking a _chunk_ argument:
- 1. Return ! PromiseCall(_transformMethod_, _transformer_, « _chunk_, _controller_ »).
- 1. Let _flushAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_transformer_, `"flush"`, *0*, « controller »).
- 1. Perform ! SetUpTransformStreamDefaultController(_stream_, _controller_, _transformAlgorithm_, _flushAlgorithm_).
-
-
-TransformStreamDefaultControllerClearAlgorithms ( controller )
-
-This abstract operation is called once the stream is closed or errored and the algorithms will not be executed any more.
-By removing the algorithm references it permits the transformer object to be garbage collected even if the
-{{TransformStream}} itself is still referenced.
-
-The results of this algorithm are not currently observable, but could become so if JavaScript eventually
-adds weak references. But even without that factor,
-implementations will likely want to include similar steps.
-
-
- 1. Set _controller_.[[transformAlgorithm]] to *undefined*.
- 1. Set _controller_.[[flushAlgorithm]] to *undefined*.
-
-
-TransformStreamDefaultControllerEnqueue ( controller, chunk )
-
-This abstract operation can be called by other specifications that wish to enqueue chunks in the readable
-side, in the same way a developer would enqueue chunks using the stream's associated controller object.
-Specifications should not do this to streams they did not create.
-
-
- 1. Let _stream_ be _controller_.[[controlledTransformStream]].
- 1. Let _readableController_ be _stream_.[[readable]].[[readableStreamController]].
- 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(_readableController_) is *false*, throw a *TypeError*
- exception.
- 1. Let _enqueueResult_ be ReadableStreamDefaultControllerEnqueue(_readableController_, _chunk_).
- 1. If _enqueueResult_ is an abrupt completion,
- 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(_stream_, _enqueueResult_.[[Value]]).
- 1. Throw _stream_.[[readable]].[[storedError]].
- 1. Let _backpressure_ be ! ReadableStreamDefaultControllerHasBackpressure(_readableController_).
- 1. If _backpressure_ is not _stream_.[[backpressure]],
- 1. Assert: _backpressure_ is *true*.
- 1. Perform ! TransformStreamSetBackpressure(_stream_, *true*).
-
-
-TransformStreamDefaultControllerError ( controller, e )
-
-This abstract operation can be called by other specifications that wish to move a transform stream to an errored state,
-in the same way a developer would error a stream using its associated controller object. Specifications should
-not do this to streams they did not create.
-
-
- 1. Perform ! TransformStreamError(_controller_.[[controlledTransformStream]], _e_).
-
-
-TransformStreamDefaultControllerPerformTransform ( controller, chunk )
-
-
- 1. Let _transformPromise_ be the result of performing _controller_.[[transformAlgorithm]], passing _chunk_.
- 1. Return the result of reacting to _transformPromise_ with the following rejection steps given the
- argument _r_:
- 1. Perform ! TransformStreamError(_controller_.[[controlledTransformStream]], _r_).
- 1. Throw _r_.
-
-
-TransformStreamDefaultControllerTerminate ( controller )
-
-This abstract operation can be called by other specifications that wish to terminate a transform stream, in the same way
-a developer-created stream would be closed by its associated controller object. Specifications should not do
-this to streams they did not create.
-
-
- 1. Let _stream_ be _controller_.[[controlledTransformStream]].
- 1. Let _readableController_ be _stream_.[[readable]].[[readableStreamController]].
- 1. Perform ! ReadableStreamDefaultControllerClose(_readableController_).
- 1. Let _error_ be a *TypeError* exception indicating that the stream has been terminated.
- 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(_stream_, _error_).
-
-
-Transform stream default sink abstract operations
-
-TransformStreamDefaultSinkWriteAlgorithm ( stream, chunk )
-
-
- 1. Assert: _stream_.[[writable]].[[state]] is `"writable"`.
- 1. Let _controller_ be _stream_.[[transformStreamController]].
- 1. If _stream_.[[backpressure]] is *true*,
- 1. Let _backpressureChangePromise_ be _stream_.[[backpressureChangePromise]].
- 1. Assert: _backpressureChangePromise_ is not *undefined*.
- 1. Return the result of reacting to _backpressureChangePromise_ with the following fulfillment steps:
- 1. Let _writable_ be _stream_.[[writable]].
- 1. Let _state_ be _writable_.[[state]].
- 1. If _state_ is `"erroring"`, throw _writable_.[[storedError]].
- 1. Assert: _state_ is `"writable"`.
- 1. Return ! TransformStreamDefaultControllerPerformTransform(_controller_, _chunk_).
- 1. Return ! TransformStreamDefaultControllerPerformTransform(_controller_, _chunk_).
-
-
-TransformStreamDefaultSinkAbortAlgorithm ( stream, reason )
-
-
- 1. Perform ! TransformStreamError(_stream_, _reason_).
- 1. Return a promise resolved with *undefined*.
-
-
-TransformStreamDefaultSinkCloseAlgorithm( stream )
-
-
- 1. Let _readable_ be _stream_.[[readable]].
- 1. Let _controller_ be _stream_.[[transformStreamController]].
- 1. Let _flushPromise_ be the result of performing _controller_.[[flushAlgorithm]].
- 1. Perform ! TransformStreamDefaultControllerClearAlgorithms(_controller_).
- 1. Return the result of reacting to _flushPromise_:
- 1. If _flushPromise_ was fulfilled, then:
- 1. If _readable_.[[state]] is `"errored"`, throw _readable_.[[storedError]].
- 1. Let _readableController_ be _readable_.[[readableStreamController]].
- 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(_readableController_) is *true*, perform !
- ReadableStreamDefaultControllerClose(_readableController_).
- 1. If _flushPromise_ was rejected with reason _r_, then:
- 1. Perform ! TransformStreamError(_stream_, _r_).
- 1. Throw _readable_.[[storedError]].
-
-
-Transform stream default source abstract operations
-
-TransformStreamDefaultSourcePullAlgorithm( stream
-)
-
-
- 1. Assert: _stream_.[[backpressure]] is *true*.
- 1. Assert: _stream_.[[backpressureChangePromise]] is not *undefined*.
- 1. Perform ! TransformStreamSetBackpressure(_stream_, *false*).
- 1. Return _stream_.[[backpressureChangePromise]].
-
-
-Other stream APIs and operations
-
-Queuing strategies
-
-The queuing strategy API
+The queuing strategy API
-
+The {{ReadableStream()}}, {{WritableStream()}}, and {{TransformStream()}} constructors all accept
+at least one argument representing an appropriate [=queuing strategy=] for the stream being
+created. Such objects contain the following properties:
-This section is non-normative.
+
+dictionary QueuingStrategy {
+ unrestricted double highWaterMark;
+ QueuingStrategySize size;
+};
-The {{ReadableStream()}}, {{WritableStream()}}, and {{TransformStream()}} constructors all accept at least one argument
-representing an appropriate queuing strategy for the stream being created. Such objects contain the following
-properties:
+callback QueuingStrategySize = unrestricted double (optional any chunk);
+
- - size(chunk) (non-byte streams only)
+ - size(chunk) (non-byte streams only)
+ -
+
A function that computes and returns the finite non-negative size of the given [=chunk=]
+ value.
+
+
The result is used to determine [=backpressure=], manifesting via the appropriate
+ desiredSize
+ property: either {{ReadableStreamDefaultController/desiredSize|defaultController.desiredSize}},
+ {{ReadableByteStreamController/desiredSize|byteController.desiredSize}}, or
+ {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}}, depending on where the queuing
+ strategy is being used. For readable streams, it also governs when the [=underlying source=]'s
+ {{UnderlyingSource/pull|pull()}} method is called.
+
+
This function has to be idempotent and not cause side effects; very strange results can occur
+ otherwise.
+
+
For [=readable byte streams=], this function is not used, as chunks are always measured in
+ bytes.
+
+
- highWaterMark
-
-
A function that computes and returns the finite non-negative size of the given chunk value.
-
- The result is used to determine backpressure, manifesting via the appropriate desiredSize
- property: either
- {{ReadableStreamDefaultController/desiredSize|defaultController.desiredSize}},
- {{ReadableByteStreamController/desiredSize|byteController.desiredSize}}, or
- {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}}, depending on where the queuing strategy is being
- used. For readable streams, it also governs when the underlying source's {{underlying source/pull()}} method
- is called.
+ A non-negative number indicating the [=high water mark=] of the stream using this queuing
+ strategy.
+
- This function has to be idempotent and not cause side effects; very strange results can occur otherwise.
+Any object with these properties can be used when a queuing strategy object is expected. However,
+we provide two built-in queuing strategy classes that provide a common vocabulary for certain
+cases: {{ByteLengthQueuingStrategy}} and {{CountQueuingStrategy}}. They both make use of the
+following Web IDL fragment for their constructors:
- For readable byte streams, this function is not used, as chunks are always measured in bytes.
-
+
+dictionary QueuingStrategyInit {
+ required unrestricted double highWaterMark;
+};
+
- highWaterMark
- -
-
A non-negative number indicating the high water mark of the stream using this queuing strategy.
-
-
+The {{ByteLengthQueuingStrategy}} class
-Any object with these properties can be used when a queuing strategy object is expected. However, we provide two
-built-in queuing strategy classes that provide a common vocabulary for certain cases: {{ByteLengthQueuingStrategy}} and
-{{CountQueuingStrategy}}.
+A common [=queuing strategy=] when dealing with bytes is to wait until the accumulated
+byteLength
properties of the incoming [=chunks=] reaches a specified high-water mark.
+As such, this is provided as a built-in [=queuing strategy=] that can be used when constructing
+streams.
+
+ When creating a [=readable stream=] or [=writable stream=], you can supply a byte-length queuing
+ strategy directly:
+
+
+ const stream = new ReadableStream(
+ { ... },
+ new ByteLengthQueuingStrategy({ highWaterMark: 16 * 1024 })
+ );
+
+
+ In this case, 16 KiB worth of [=chunks=] can be enqueued by the readable stream's [=underlying
+ source=] before the readable stream implementation starts sending [=backpressure=] signals to the
+ underlying source.
+
+
+ const stream = new WritableStream(
+ { ... },
+ new ByteLengthQueuingStrategy({ highWaterMark: 32 * 1024 })
+ );
+
+
+ In this case, 32 KiB worth of [=chunks=] can be accumulated in the writable stream's internal
+ queue, waiting for previous writes to the [=underlying sink=] to finish, before the writable
+ stream starts sending [=backpressure=] signals to any [=producers=].
-Class ByteLengthQueuingStrategy
+It is not necessary to use {{ByteLengthQueuingStrategy}} with [=readable byte
+streams=], as they always measure chunks in bytes. Attempting to construct a byte stream with a
+{{ByteLengthQueuingStrategy}} will fail.
-A common queuing strategy when dealing with bytes is to wait until the accumulated byteLength
-properties of the incoming chunks reaches a specified high-water mark. As such, this is provided as a built-in
-queuing strategy that can be used when constructing streams.
+
Interface definition
-
- When creating a readable stream or writable stream, you can supply a byte-length queuing strategy
- directly:
+The Web IDL definition for the {{ByteLengthQueuingStrategy}} class is given as follows:
+
+
+[Exposed=(Window,Worker,Worklet)]
+interface ByteLengthQueuingStrategy {
+ constructor(QueuingStrategyInit init);
-
- const stream = new ReadableStream(
- { ... },
- new ByteLengthQueuingStrategy({ highWaterMark: 16 * 1024 })
- );
-
+ attribute unrestricted double highWaterMark;
+ readonly attribute Function size;
+};
+
- In this case, 16 KiB worth of chunks can be enqueued by the readable stream's underlying source before
- the readable stream implementation starts sending backpressure signals to the underlying source.
+Internal slots
-
- const stream = new WritableStream(
- { ... },
- new ByteLengthQueuingStrategy({ highWaterMark: 32 * 1024 })
- );
-
+Instances of {{ByteLengthQueuingStrategy}} have a \[[highWaterMark]] internal slot, storing the
+value given in the constructor.
- In this case, 32 KiB worth of chunks can be accumulated in the writable stream's internal queue, waiting for
- previous writes to the underlying sink to finish, before the writable stream starts sending
- backpressure signals to any producers.
+
+ Additionally, every [=/global object=] |globalObject| has an associated byte length queuing
+ strategy size function, which is a {{Function}} whose value must be initialized as follows:
+
+ 1. Let |steps| be the following steps, given |chunk|:
+ 1. Return ? [$GetV$](|chunk|, "`byteLength`").
+ 1. Let |F| be ! [$CreateBuiltinFunction$](|steps|, « », |globalObject|'s [=relevant Realm=]).
+ 1. Perform ! [$SetFunctionName$](|F|, "`size`").
+ 1. Perform ! [$SetFunctionLength$](|F|, 1).
+ 1. Set |globalObject|'s [=byte length queuing strategy size function=] to a {{Function}} that
+ represents a reference to |F|. (The [=callback context=] does not matter, since this function is
+ never [=invoked=].)
+
+ This design is somewhat historical. It is motivated by the desire to ensure that
+ {{ByteLengthQueuingStrategy/size}} is a function, not a method, i.e. it does not check its
+ this
value. See whatwg/streams#1005 and heycam/webidl#819 for more background.
-
- It is not necessary to use {{ByteLengthQueuingStrategy}} with readable byte streams, as they always measure
- chunks in bytes. Attempting to construct a byte stream with a {{ByteLengthQueuingStrategy}} will fail.
-
+Constructor and properties
-Class definition
+
+ strategy = new {{ByteLengthQueuingStrategy/constructor(init)|ByteLengthQueuingStrategy}}({ {{QueuingStrategyInit/highWaterMark}} })
+ -
+
Creates a new {{ByteLengthQueuingStrategy}} with the provided [=high water mark=].
-
+ Note that the provided high water mark will not be validated ahead of time. Instead, if it is
+ negative, NaN, or not a number, the resulting {{ByteLengthQueuingStrategy}} will cause the
+ corresponding stream constructor to throw.
-This section is non-normative.
+
highWaterMark = strategy.{{ByteLengthQueuingStrategy/highWaterMark}}
+ strategy.{{ByteLengthQueuingStrategy/highWaterMark}} = highWaterMark
+ -
+
Returns the [=high water mark=] provided to the constructor, or sets it to a new value.
-If one were to write the {{ByteLengthQueuingStrategy}} class in something close to the syntax of [[!ECMASCRIPT]], it
-would look like
+
Note that high water marks are only read from [=queuing strategies=] upon stream construction,
+ i.e. setting this property will not change the behavior of any already-constructed streams.
-
- class ByteLengthQueuingStrategy {
- constructor({ highWaterMark })
- size(chunk)
- }
-
+ strategy.{{ByteLengthQueuingStrategy/size}}(chunk)
+ -
+
Measures the size of chunk by returning the value of its
+ byteLength
property.
+
-Each {{ByteLengthQueuingStrategy}} instance will additionally have an own data property
-highWaterMark
set by its constructor.
+
+ The ByteLengthQueuingStrategy(|init|) constructor steps are:
+ 1. Set [=this=].\[[highWaterMark]] to |init|["{{QueuingStrategyInit/highWaterMark}}"].
-new
-ByteLengthQueuingStrategy({ highWaterMark })
+
+ The highWaterMark
+ attribute's getter steps are:
-
- The constructor takes a non-negative number for the high-water mark, and stores it on the object as a property.
+ 1. Return [=this=].\[[highWaterMark]].
+
+ Its setter steps are:
+
+ 1. Set [=this=].\[[highWaterMark]] to [=the given value=].
-
- 1. Perform ! CreateDataProperty(*this*, `"highWaterMark"`, _highWaterMark_).
-
+
+ The size attribute's getter
+ steps are:
-Properties of the {{ByteLengthQueuingStrategy}} prototype
+ 1. Return [=this=]'s [=relevant global object=]'s [=byte length queuing strategy size function=].
+
-size(chunk)
+The {{CountQueuingStrategy}} class
-
- The size
method returns the given chunk's byteLength
property. (If the chunk doesn't have
- one, it will return undefined , causing the stream using this strategy to error.)
+A common [=queuing strategy=] when dealing with streams of generic objects is to simply count the
+number of chunks that have been accumulated so far, waiting until this number reaches a specified
+high-water mark. As such, this strategy is also provided out of the box.
- This method is intentionally generic; it does not require that its this value be a
- ByteLengthQueuingStrategy
object.
+
+ When creating a [=readable stream=] or [=writable stream=], you can supply a count queuing
+ strategy directly:
+
+
+ const stream = new ReadableStream(
+ { ... },
+ new CountQueuingStrategy({ highWaterMark: 10 })
+ );
+
+
+ In this case, 10 [=chunks=] (of any kind) can be enqueued by the readable stream's [=underlying
+ source=] before the readable stream implementation starts sending [=backpressure=] signals to the
+ underlying source.
+
+
+ const stream = new WritableStream(
+ { ... },
+ new CountQueuingStrategy({ highWaterMark: 5 })
+ );
+
+
+ In this case, five [=chunks=] (of any kind) can be accumulated in the writable stream's internal
+ queue, waiting for previous writes to the [=underlying sink=] to finish, before the writable
+ stream starts sending [=backpressure=] signals to any [=producers=].
-
- 1. Return ? GetV(_chunk_, `"byteLength"`).
-
+Interface definition
-Class CountQueuingStrategy
+The Web IDL definition for the {{CountQueuingStrategy}} class is given as follows:
-A common queuing strategy when dealing with streams of generic objects is to simply count the number of chunks
-that have been accumulated so far, waiting until this number reaches a specified high-water mark. As such, this
-strategy is also provided out of the box.
+
+[Exposed=(Window,Worker,Worklet)]
+interface CountQueuingStrategy {
+ constructor(QueuingStrategyInit init);
-
- When creating a readable stream or writable stream, you can supply a count queuing strategy directly:
+ attribute unrestricted double highWaterMark;
+ readonly attribute Function size;
+};
+
-
- const stream = new ReadableStream(
- { ... },
- new CountQueuingStrategy({ highWaterMark: 10 })
- );
-
+Internal slots
- In this case, 10 chunks (of any kind) can be enqueued by the readable stream's underlying source before
- the readable stream implementation starts sending backpressure signals to the underlying source.
+Instances of {{CountQueuingStrategy}} have a \[[highWaterMark]] internal slot, storing the
+value given in the constructor.
-
- const stream = new WritableStream(
- { ... },
- new CountQueuingStrategy({ highWaterMark: 5 })
- );
-
+
+ Additionally, every [=/global object=] |globalObject| has an associated count queuing strategy
+ size function, which is a {{Function}} whose value must be initialized as follows:
- In this case, five chunks (of any kind) can be accumulated in the writable stream's internal queue, waiting
- for previous writes to the underlying sink to finish, before the writable stream starts sending
- backpressure signals to any producers.
+ 1. Let |steps| be the following steps:
+ 1. Return 1.
+ 1. Let |F| be ! [$CreateBuiltinFunction$](|steps|, « », |globalObject|'s [=relevant Realm=]).
+ 1. Perform ! [$SetFunctionName$](|F|, "`size`").
+ 1. Perform ! [$SetFunctionLength$](|F|, 0).
+ 1. Set |globalObject|'s [=count queuing strategy size function=] to a {{Function}} that represents
+ a reference to |F|. (The [=callback context=] does not matter, since this function is never
+ [=invoked=].)
+
+ This design is somewhat historical. It is motivated by the desire to ensure that
+ {{CountQueuingStrategy/size}} is a function, not a method, i.e. it does not check its
+ this
value. See whatwg/streams#1005 and heycam/webidl#819 for more background.
-Class definition
+Constructor and properties
-
+
+ strategy = new {{CountQueuingStrategy/constructor(init)|CountQueuingStrategy}}({ {{QueuingStrategyInit/highWaterMark}} })
+ -
+
Creates a new {{CountQueuingStrategy}} with the provided [=high water mark=].
-This section is non-normative.
+
Note that the provided high water mark will not be validated ahead of time. Instead, if it is
+ negative, NaN, or not a number, the resulting {{CountQueuingStrategy}} will cause the
+ corresponding stream constructor to throw.
-If one were to write the {{CountQueuingStrategy}} class in something close to the syntax of [[!ECMASCRIPT]], it would
-look like
+
highWaterMark = strategy.{{CountQueuingStrategy/highWaterMark}}
+ strategy.{{CountQueuingStrategy/highWaterMark}} = highWaterMark
+ -
+
Returns the [=high water mark=] provided to the constructor, or sets it to a new value.
-
- class CountQueuingStrategy {
- constructor({ highWaterMark })
- size(chunk)
- }
-
+ Note that high water marks are only read from [=queuing strategies=] upon stream construction,
+ i.e. setting this property will not change the behavior of any already-constructed streams.
+
+
strategy.{{CountQueuingStrategy/size}}(chunk)
+ -
+
Measures the size of chunk by always returning 1. This ensures that the total
+ queue size is a count of the number of chunks in the queue.
+
-Each {{CountQueuingStrategy}} instance will additionally have an own data property highWaterMark
-set by its constructor.
+
+ The CountQueuingStrategy(|init|) constructor steps are:
+ 1. Set [=this=].\[[highWaterMark]] to |init|["{{QueuingStrategyInit/highWaterMark}}"].
-new
-CountQueuingStrategy({ highWaterMark })
+
+ The highWaterMark
+ attribute's getter steps are:
-
- The constructor takes a non-negative number for the high-water mark, and stores it on the object as a property.
+ 1. Return [=this=].\[[highWaterMark]].
+
+ Its setter steps are:
+
+ 1. Set [=this=].\[[highWaterMark]] to [=the given value=].
-
- 1. Perform ! CreateDataProperty(*this*, `"highWaterMark"`, _highWaterMark_).
-
+
+ The size attribute's getter
+ steps are:
-Properties of the {{CountQueuingStrategy}} prototype
+ 1. Return [=this=]'s [=relevant global object=]'s [=count queuing strategy size function=].
+
-size()
+Abstract operations
-
- The size
method returns one always, so that the total queue size is a count of the number of chunks in
- the queue.
-
- This method is intentionally generic; it does not require that its this value be a
- CountQueuingStrategy
object.
-
-
-
- 1. Return *1*.
-
-
-Queue-with-sizes operations
-
-The streams in this specification use a "queue-with-sizes" data structure to store queued up values, along with their
-determined sizes. Various specification objects contain a queue-with-sizes, represented by the object having two paired
-internal slots, always named \[[queue]] and \[[queueTotalSize]]. \[[queue]] is a List of Records with \[[value]] and
-\[[size]] fields, and \[[queueTotalSize]] is a JavaScript {{Number}}, i.e. a double-precision floating point number.
-
-The following abstract operations are used when operating on objects that contain queues-with-sizes, in order to ensure
-that the two internal slots stay synchronized.
-
-Due to the limited precision of floating-point arithmetic, the framework specified here, of keeping a
-running total in the \[[queueTotalSize]] slot, is not equivalent to adding up the size of all chunks in
-\[[queue]]. (However, this only makes a difference when there is a huge (~1015) variance in size between
-chunks, or when trillions of chunks are enqueued.)
-
-DequeueValue ( container )
-
-
- 1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots.
- 1. Assert: _container_.[[queue]] is not empty.
- 1. Let _pair_ be the first element of _container_.[[queue]].
- 1. Remove _pair_ from _container_.[[queue]], shifting all other elements downward (so that the second becomes the
- first, and so on).
- 1. Set _container_.[[queueTotalSize]] to _container_.[[queueTotalSize]] − _pair_.[[size]].
- 1. If _container_.[[queueTotalSize]] < *0*, set _container_.[[queueTotalSize]] to *0*. (This can occur due to
- rounding errors.)
- 1. Return _pair_.[[value]].
-
-
-EnqueueValueWithSize ( container,
-value, size )
-
-
- 1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots.
- 1. Let _size_ be ? ToNumber(_size_).
- 1. If ! IsFiniteNonNegativeNumber(_size_) is *false*, throw a *RangeError* exception.
- 1. Append Record {[[value]]: _value_, [[size]]: _size_} as the last element of _container_.[[queue]].
- 1. Set _container_.[[queueTotalSize]] to _container_.[[queueTotalSize]] + _size_.
-
-
-PeekQueueValue ( container )
-
-
- 1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots.
- 1. Assert: _container_.[[queue]] is not empty.
- 1. Let _pair_ be the first element of _container_.[[queue]].
- 1. Return _pair_.[[value]].
-
-
-ResetQueue ( container )
-
-
- 1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots.
- 1. Set _container_.[[queue]] to a new empty List.
- 1. Set _container_.[[queueTotalSize]] to *0*.
-
-
-Miscellaneous operations
-
-A few abstract operations are used in this specification for utility purposes. We define them here.
-
-CreateAlgorithmFromUnderlyingMethod ( underlyingObject, methodName,
-algoArgCount, extraArgs )
-
-
- 1. Assert: _underlyingObject_ is not *undefined*.
- 1. Assert: ! IsPropertyKey(_methodName_) is *true*.
- 1. Assert: _algoArgCount_ is *0* or *1*.
- 1. Assert: _extraArgs_ is a List.
- 1. Let _method_ be ? GetV(_underlyingObject_, _methodName_).
- 1. If _method_ is not *undefined*,
- 1. If ! IsCallable(_method_) is *false*, throw a *TypeError* exception.
- 1. If _algoArgCount_ is *0*, return an algorithm that performs the following steps:
- 1. Return ! PromiseCall(_method_, _underlyingObject_, _extraArgs_).
- 1. Otherwise, return an algorithm that performs the following steps, taking an _arg_ argument:
- 1. Let _fullArgs_ be a List consisting of _arg_ followed by the elements of _extraArgs_ in order.
- 1. Return ! PromiseCall(_method_, _underlyingObject_, _fullArgs_).
- 1. Return an algorithm which returns a promise resolved with *undefined*.
-
-
-InvokeOrNoop ( O, P, args
-)
+The following algorithms are used by the stream constructors to extract the relevant pieces from
+a {{QueuingStrategy}} dictionary.
-
- InvokeOrNoop is a slight modification of the [[!ECMASCRIPT]] Invoke abstract operation to return
- undefined when the method is not present.
+
+ ExtractHighWaterMark(|strategy|, |defaultHWM|)
+ performs the following steps:
+
+ 1. If |strategy|["{{QueuingStrategy/highWaterMark}}"] does not [=map/exist=], return |defaultHWM|.
+ 1. Let |highWaterMark| be |strategy|["{{QueuingStrategy/highWaterMark}}"].
+ 1. If |highWaterMark| is NaN or |highWaterMark| < 0, throw a {{RangeError}} exception.
+ 1. Return |highWaterMark|.
+
+ +∞ is explicitly allowed as a valid [=high water mark=]. It causes [=backpressure=]
+ to never be applied.
+
+
+
+ ExtractSizeAlgorithm(|strategy|)
+ performs the following steps:
+
+ 1. If |strategy|["{{QueuingStrategy/size}}"] does not [=map/exist=], return an algorithm that
+ returns 1.
+ 1. Return an algorithm that performs the following steps, taking a |chunk| argument:
+ 1. Return the result of [=invoke|invoking=] |strategy|["{{QueuingStrategy/size}}"] with argument
+ list « |chunk| ».
-
- 1. Assert: _O_ is not *undefined*.
- 1. Assert: ! IsPropertyKey(_P_) is *true*.
- 1. Assert: _args_ is a List.
- 1. Let _method_ be ? GetV(_O_, _P_).
- 1. If _method_ is *undefined*, return *undefined*.
- 1. Return ? Call(_method_, _O_, _args_).
-
+Supporting abstract operations
+
+The following abstract operations each support the implementation of more than one type of stream,
+and as such are not grouped under the major sections above.
+
+Queue-with-sizes
+
+The streams in this specification use a "queue-with-sizes" data structure to store queued up
+values, along with their determined sizes. Various specification objects contain a
+queue-with-sizes, represented by the object having two paired internal slots, always named
+\[[queue]] and \[[queueTotalSize]]. \[[queue]] is a [=list=] of Records with \[[value]] and
+\[[size]] fields, and \[[queueTotalSize]] is a JavaScript {{Number}}, i.e. a double-precision
+floating point number.
+
+The following abstract operations are used when operating on objects that contain
+queues-with-sizes, in order to ensure that the two internal slots stay synchronized.
+
+
Due to the limited precision of floating-point arithmetic, the framework
+specified here, of keeping a running total in the \[[queueTotalSize]] slot, is not
+equivalent to adding up the size of all [=chunks=] in \[[queue]]. (However, this only makes a
+difference when there is a huge (~1015) variance in size between chunks, or when
+trillions of chunks are enqueued.)
+
+
+ DequeueValue(|container|) performs the
+ following steps:
+
+ 1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots.
+ 1. Assert: |container|.\[[queue]] is not [=list/is empty|empty=].
+ 1. Let |pair| be |container|.\[[queue]][0].
+ 1. [=list/Remove=] |pair| from |container|.\[[queue]].
+ 1. Set |container|.\[[queueTotalSize]] to |container|.\[[queueTotalSize]] − |pair|.\[[size]].
+ 1. If |container|.\[[queueTotalSize]] < 0, set |container|.\[[queueTotalSize]] to 0. (This can
+ occur due to rounding errors.)
+ 1. Return |pair|.\[[value]].
+
-IsFiniteNonNegativeNumber ( v
-)
+
+ EnqueueValueWithSize(|container|, |value|, |size|) performs the
+ following steps:
-
- 1. If ! IsNonNegativeNumber(_v_) is *false*, return *false*.
- 1. If _v_ is *+∞*, return *false*.
- 1. Return *true*.
-
+ 1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots.
+ 1. If ! [$IsNonNegativeNumber$](|v|) is false, throw a {{RangeError}} exception.
+ 1. If |v| is +∞, throw a {{RangeError}} exception.
+ 1. [=list/Append=] Record {\[[value]]: |value|, \[[size]]: |size|} to |container|.\[[queue]].
+ 1. Set |container|.\[[queueTotalSize]] to |container|.\[[queueTotalSize]] + |size|.
+
-IsNonNegativeNumber ( v )
+
+ PeekQueueValue(|container|)
+ performs the following steps:
-
- 1. If Type(_v_) is not Number, return *false*.
- 1. If _v_ is *NaN*, return *false*.
- 1. If _v_ < *0*, return *false*.
- 1. Return *true*.
-
+ 1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots.
+ 1. Assert: |container|.\[[queue]] is not [=list/is empty|empty=].
+ 1. Let |pair| be |container|.\[[queue]][0].
+ 1. Return |pair|.\[[value]].
+
-PromiseCall ( F, V,
-args )
+
+ ResetQueue(|container|)
+ performs the following steps:
-
- This encapsulates the relevant promise-related parts of the Web IDL call a user object's operation algorithm
- for use while we work on moving to Web IDL.
-
-
-
- 1. Assert: ! IsCallable(_F_) is *true*.
- 1. Assert: _V_ is not *undefined*.
- 1. Assert: _args_ is a List.
- 1. Let _returnValue_ be Call(_F_, _V_, _args_).
- 1. If _returnValue_ is an abrupt completion, return a promise rejected with _returnValue_.[[Value]].
- 1. Otherwise, return a promise resolved with _returnValue_.[[Value]].
-
-
-TransferArrayBuffer ( O )
-
-
- 1. Assert: Type(_O_) is Object.
- 1. Assert: _O_ has an [[ArrayBufferData]] internal slot.
- 1. Assert: ! IsDetachedBuffer(_O_) is *false*.
- 1. Let _arrayBufferData_ be _O_.[[ArrayBufferData]].
- 1. Let _arrayBufferByteLength_ be _O_.[[ArrayBufferByteLength]].
- 1. Perform ! DetachArrayBuffer(_O_).
- 1. Return a new ArrayBuffer
object (created in the current Realm Record) whose
- [[ArrayBufferData]] internal slot value is _arrayBufferData_ and whose [[ArrayBufferByteLength]] internal slot
- value is _arrayBufferByteLength_.
-
-
-ValidateAndNormalizeHighWaterMark ( highWaterMark )
-
-
- 1. Set _highWaterMark_ to ? ToNumber(_highWaterMark_).
- 1. If _highWaterMark_ is *NaN* or _highWaterMark_ < *0*, throw a *RangeError* exception.
- *+∞* is explicitly allowed as a valid high water mark. It causes backpressure to never be applied.
- 1. Return _highWaterMark_.
-
-
-MakeSizeAlgorithmFromSizeFunction ( size )
-
-
- 1. If _size_ is *undefined*, return an algorithm that returns *1*.
- 1. If ! IsCallable(_size_) is *false*, throw a *TypeError* exception.
- 1. Return an algorithm that performs the following steps, taking a _chunk_ argument:
- 1. Return ? Call(_size_, *undefined*, « _chunk_ »).
-
-
-Global properties
-
-The following constructors must be exposed on the global object as data properties of the same name:
-
-
- - {{ReadableStream}}
-
- {{WritableStream}}
-
- {{TransformStream}}
-
- {{ByteLengthQueuingStrategy}}
-
- {{CountQueuingStrategy}}
-
-
-The attributes of these properties must be { \[[Writable]]: true , \[[Enumerable]]: false ,
-\[[Configurable]]: true }.
+ 1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots.
+ 1. Set |container|.\[[queue]] to a new empty [=list=].
+ 1. Set |container|.\[[queueTotalSize]] to 0.
+
-
- The {{ReadableStreamDefaultReader}}, {{ReadableStreamBYOBReader}}, {{ReadableStreamDefaultController}},
- {{ReadableByteStreamController}}, {{WritableStreamDefaultWriter}}, {{WritableStreamDefaultController}}, and
- {{TransformStreamDefaultController}} classes are specifically not exposed, as they are not independently useful.
+Miscellaneous
+
+The following abstract operations are a grab-bag of utilities.
+
+
+ IsNonNegativeNumber(|v|) performs the following steps:
+
+ 1. If [$Type$](|v|) is not Number, return false.
+ 1. If |v| is NaN, return false.
+ 1. If |v| < 0, return false.
+ 1. Return true.
+
+
+
+ TransferArrayBuffer(|O|) performs the following steps:
+
+ 1. Assert: [$Type$](|O|) is Object.
+ 1. Assert: |O| has an \[[ArrayBufferData]] internal slot.
+ 1. Assert: ! [$IsDetachedBuffer$](|O|) is false.
+ 1. Let |arrayBufferData| be |O|.\[[ArrayBufferData]].
+ 1. Let |arrayBufferByteLength| be |O|.\[[ArrayBufferByteLength]].
+ 1. Perform ! [$DetachArrayBuffer$](|O|).
+ 1. Return a new {{ArrayBuffer}} object, created in [=the current Realm=], whose
+ \[[ArrayBufferData]] internal slot value is |arrayBufferData| and whose
+ \[[ArrayBufferByteLength]] internal slot value is |arrayBufferByteLength|.
Examples of creating streams
@@ -5458,663 +5602,654 @@ The attributes of these properties must be { \[[Writable]]: trueThis section, and all its subsections, are non-normative.
-The previous examples throughout the standard have focused on how to use streams. Here we show how to create a stream,
-using the {{ReadableStream}} or {{WritableStream}} constructors.
+The previous examples throughout the standard have focused on how to use streams. Here we show how
+to create a stream, using the {{ReadableStream}}, {{WritableStream}}, and {{TransformStream}}
+constructors.
-A readable stream with an underlying push source (no backpressure support)
+A readable stream with an underlying push source (no
+backpressure support)
-The following function creates readable streams that wrap {{WebSocket}} instances [[HTML]], which are push sources
-that do not support backpressure signals. It illustrates how, when adapting a push source, usually most of the work
-happens in the {{underlying source/start()}} function.
+The following function creates [=readable streams=] that wrap {{WebSocket}} instances [[HTML]],
+which are [=push sources=] that do not support backpressure signals. It illustrates how, when
+adapting a push source, usually most of the work happens in the {{UnderlyingSource/start|start()}}
+method.
-
- function makeReadableWebSocketStream(url, protocols) {
- const ws = new WebSocket(url, protocols);
- ws.binaryType = "arraybuffer";
+
+function makeReadableWebSocketStream(url, protocols) {
+ const ws = new WebSocket(url, protocols);
+ ws.binaryType = "arraybuffer";
- return new ReadableStream({
- start(controller) {
- ws.onmessage = event => controller.enqueue(event.data);
- ws.onclose = () => controller.close();
- ws.onerror = () => controller.error(new Error("The WebSocket errored!"));
- },
+ return new ReadableStream({
+ start(controller) {
+ ws.onmessage = event => controller.enqueue(event.data);
+ ws.onclose = () => controller.close();
+ ws.onerror = () => controller.error(new Error("The WebSocket errored!"));
+ },
- cancel() {
- ws.close();
- }
- });
- }
-
+ cancel() {
+ ws.close();
+ }
+ });
+}
+
-We can then use this function to create readable streams for a web socket, and pipe that stream to an arbitrary
-writable stream:
+We can then use this function to create readable streams for a web socket, and pipe that stream to
+an arbitrary writable stream:
-
- const webSocketStream = makeReadableWebSocketStream("wss://example.com:443/", "protocol");
+
+const webSocketStream = makeReadableWebSocketStream("wss://example.com:443/", "protocol");
- webSocketStream.pipeTo(writableStream)
- .then(() => console.log("All data successfully written!"))
- .catch(e => console.error("Something went wrong!", e));
-
+webSocketStream.pipeTo(writableStream)
+ .then(() => console.log("All data successfully written!"))
+ .catch(e => console.error("Something went wrong!", e));
+
- This specific style of wrapping a web socket interprets web socket messages directly as chunks.
- This can be a convenient abstraction, for example when piping to a writable stream or transform
- stream for which each web socket message makes sense as a chunk to consume or transform.
-
- However, often when people talk about "adding streams support to web sockets", they are hoping instead for a new
- capability to send an individual web socket message in a streaming fashion, so that e.g. a file could be transferred
- in a single message without holding all of its contents in memory on the client side. To accomplish this goal, we'd
- instead want to allow individual web socket messages to themselves be {{ReadableStream}} instances. That isn't what we
- show in the above example.
-
- For more background, see this
- discussion.
+ This specific style of wrapping a web socket interprets web socket messages directly as
+ [=chunks=]. This can be a convenient abstraction, for example when [=piping=] to a [=writable
+ stream=] or [=transform stream=] for which each web socket message makes sense as a chunk to
+ consume or transform.
+
+ However, often when people talk about "adding streams support to web sockets", they are hoping
+ instead for a new capability to send an individual web socket message in a streaming fashion, so
+ that e.g. a file could be transferred in a single message without holding all of its contents in
+ memory on the client side. To accomplish this goal, we'd instead want to allow individual web
+ socket messages to themselves be {{ReadableStream}} instances. That isn't what we show in the
+ above example.
+
+ For more background, see this discussion.
-A readable stream with an underlying push source and backpressure support
+A readable stream with an underlying push source and
+backpressure support
-The following function returns readable streams that wrap "backpressure sockets," which are hypothetical objects
-that have the same API as web sockets, but also provide the ability to pause and resume the flow of data with their
-readStop
and readStart
methods. In doing so, this example shows how to apply
-backpressure to underlying sources that support it.
+The following function returns [=readable streams=] that wrap "backpressure sockets," which are
+hypothetical objects that have the same API as web sockets, but also provide the ability to pause
+and resume the flow of data with their readStop
and readStart
methods. In
+doing so, this example shows how to apply [=backpressure=] to [=underlying sources=] that support
+it.
-
- function makeReadableBackpressureSocketStream(host, port) {
- const socket = createBackpressureSocket(host, port);
+
+function makeReadableBackpressureSocketStream(host, port) {
+ const socket = createBackpressureSocket(host, port);
- return new ReadableStream({
- start(controller) {
- socket.ondata = event => {
- controller.enqueue(event.data);
+ return new ReadableStream({
+ start(controller) {
+ socket.ondata = event => {
+ controller.enqueue(event.data);
- if (controller.desiredSize <= 0) {
- // The internal queue is full, so propagate
- // the backpressure signal to the underlying source.
- socket.readStop();
- }
- };
+ if (controller.desiredSize <= 0) {
+ // The internal queue is full, so propagate
+ // the backpressure signal to the underlying source.
+ socket.readStop();
+ }
+ };
- socket.onend = () => controller.close();
- socket.onerror = () => controller.error(new Error("The socket errored!"));
- },
+ socket.onend = () => controller.close();
+ socket.onerror = () => controller.error(new Error("The socket errored!"));
+ },
- pull() {
- // This is called if the internal queue has been emptied, but the
- // stream's consumer still wants more data. In that case, restart
- // the flow of data if we have previously paused it.
- socket.readStart();
- },
+ pull() {
+ // This is called if the internal queue has been emptied, but the
+ // stream's consumer still wants more data. In that case, restart
+ // the flow of data if we have previously paused it.
+ socket.readStart();
+ },
- cancel() {
- socket.close();
- }
- });
- }
-
-
-We can then use this function to create readable streams for such "backpressure sockets" in the same way we do for web
-sockets. This time, however, when we pipe to a destination that cannot accept data as fast as the socket is producing
-it, or if we leave the stream alone without reading from it for some time, a backpressure signal will be sent to the
-socket.
-
-A readable byte stream with an underlying push source (no backpressure support)
-
-The following function returns readable byte streams that wraps a hypothetical UDP socket API, including a
-promise-returning select2()
method that is meant to be evocative of the POSIX select(2) system call.
-
-Since the UDP protocol does not have any built-in backpressure support, the backpressure signal given by
-{{ReadableByteStreamController/desiredSize}} is ignored, and the stream ensures that when data is available from the
-socket but not yet requested by the developer, it is enqueued in the stream's internal queue, to avoid overflow
-of the kernel-space queue and a consequent loss of data.
-
-This has some interesting consequences for how consumers interact with the stream. If the consumer does not read
-data as fast as the socket produces it, the chunks will remain in the stream's internal queue
-indefinitely. In this case, using a BYOB reader will cause an extra copy, to move the data from the stream's
-internal queue to the developer-supplied buffer. However, if the consumer consumes the data quickly enough, a BYOB
-reader will allow zero-copy reading directly into developer-supplied buffers.
-
-(You can imagine a more complex version of this example which uses {{ReadableByteStreamController/desiredSize}} to
-inform an out-of-band backpressure signaling mechanism, for example by sending a message down the socket to adjust the
-rate of data being sent. That is left as an exercise for the reader.)
-
-
- const DEFAULT_CHUNK_SIZE = 65536;
-
- function makeUDPSocketStream(host, port) {
- const socket = createUDPSocket(host, port);
-
- return new ReadableStream({
- type: "bytes",
-
- start(controller) {
- readRepeatedly().catch(e => controller.error(e));
-
- function readRepeatedly() {
- return socket.select2().then(() => {
- // Since the socket can become readable even when there’s
- // no pending BYOB requests, we need to handle both cases.
- let bytesRead;
- if (controller.byobRequest) {
- const v = controller.byobRequest.view;
- bytesRead = socket.readInto(v.buffer, v.byteOffset, v.byteLength);
- controller.byobRequest.respond(bytesRead);
- } else {
- const buffer = new ArrayBuffer(DEFAULT_CHUNK_SIZE);
- bytesRead = socket.readInto(buffer, 0, DEFAULT_CHUNK_SIZE);
- controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
- }
-
- if (bytesRead === 0) {
- controller.close();
- return;
- }
-
- return readRepeatedly();
- });
- }
- },
+ cancel() {
+ socket.close();
+ }
+ });
+}
+
- cancel() {
- socket.close();
- }
- });
- }
-
+We can then use this function to create readable streams for such "backpressure sockets" in the
+same way we do for web sockets. This time, however, when we pipe to a destination that cannot
+accept data as fast as the socket is producing it, or if we leave the stream alone without reading
+from it for some time, a backpressure signal will be sent to the socket.
-{{ReadableStream}} instances returned from this function can now vend BYOB readers, with all of the
-aforementioned benefits and caveats.
+A readable byte stream with an underlying push source (no backpressure
+support)
-A readable stream with an underlying pull source
+The following function returns [=readable byte streams=] that wraps a hypothetical UDP socket API,
+including a promise-returning select2()
method that is meant to be evocative of the
+POSIX select(2) system call.
-The following function returns readable streams that wrap portions of the
-Node.js file system API (which themselves map fairly directly to C's
-fopen
, fread
, and fclose
trio). Files are a typical example of pull
-sources. Note how in contrast to the examples with push sources, most of the work here happens on-demand in the
-{{underlying source/pull()}} function, and not at startup time in the {{underlying source/start()}} function.
+Since the UDP protocol does not have any built-in backpressure support, the backpressure signal
+given by {{ReadableByteStreamController/desiredSize}} is ignored, and the stream ensures that when
+data is available from the socket but not yet requested by the developer, it is enqueued in the
+stream's [=internal queue=], to avoid overflow of the kernel-space queue and a consequent loss of
+data.
-
- const fs = require("pr/fs"); // https://github.com/jden/pr
- const CHUNK_SIZE = 1024;
+This has some interesting consequences for how [=consumers=] interact with the stream. If the
+consumer does not read data as fast as the socket produces it, the [=chunks=] will remain in the
+stream's [=internal queue=] indefinitely. In this case, using a [=BYOB reader=] will cause an extra
+copy, to move the data from the stream's internal queue to the developer-supplied buffer. However,
+if the consumer consumes the data quickly enough, a [=BYOB reader=] will allow zero-copy reading
+directly into developer-supplied buffers.
- function makeReadableFileStream(filename) {
- let fd;
- let position = 0;
+(You can imagine a more complex version of this example which uses
+{{ReadableByteStreamController/desiredSize}} to inform an out-of-band backpressure signaling
+mechanism, for example by sending a message down the socket to adjust the rate of data being sent.
+That is left as an exercise for the reader.)
- return new ReadableStream({
- start() {
- return fs.open(filename, "r").then(result => {
- fd = result;
- });
- },
+
+const DEFAULT_CHUNK_SIZE = 65536;
- pull(controller) {
- const buffer = new ArrayBuffer(CHUNK_SIZE);
+function makeUDPSocketStream(host, port) {
+ const socket = createUDPSocket(host, port);
- return fs.read(fd, buffer, 0, CHUNK_SIZE, position).then(bytesRead => {
- if (bytesRead === 0) {
- return fs.close(fd).then(() => controller.close());
+ return new ReadableStream({
+ type: "bytes",
+
+ start(controller) {
+ readRepeatedly().catch(e => controller.error(e));
+
+ function readRepeatedly() {
+ return socket.select2().then(() => {
+ // Since the socket can become readable even when there’s
+ // no pending BYOB requests, we need to handle both cases.
+ let bytesRead;
+ if (controller.byobRequest) {
+ const v = controller.byobRequest.view;
+ bytesRead = socket.readInto(v.buffer, v.byteOffset, v.byteLength);
+ controller.byobRequest.respond(bytesRead);
} else {
- position += bytesRead;
+ const buffer = new ArrayBuffer(DEFAULT_CHUNK_SIZE);
+ bytesRead = socket.readInto(buffer, 0, DEFAULT_CHUNK_SIZE);
controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
}
- });
- },
- cancel() {
- return fs.close(fd);
- }
- });
- }
-
+ if (bytesRead === 0) {
+ controller.close();
+ return;
+ }
-We can then create and use readable streams for files just as we could before for sockets.
+ return readRepeatedly();
+ });
+ }
+ },
-A readable byte stream with an underlying pull source
+ cancel() {
+ socket.close();
+ }
+ });
+}
+
-The following function returns readable byte streams that allow efficient zero-copy reading of files, again
-using the Node.js file system API. Instead of using a predetermined chunk
-size of 1024, it attempts to fill the developer-supplied buffer, allowing full control.
+{{ReadableStream}} instances returned from this function can now vend [=BYOB readers=], with all of
+the aforementioned benefits and caveats.
-
- const fs = require("pr/fs"); // https://github.com/jden/pr
- const DEFAULT_CHUNK_SIZE = 1024;
+A readable stream with an underlying pull source
- function makeReadableByteFileStream(filename) {
- let fd;
- let position = 0;
+The following function returns [=readable streams=] that wrap portions of the Node.js file system API (which themselves map fairly
+directly to C's fopen
, fread
, and fclose
trio). Files are a
+typical example of [=pull sources=]. Note how in contrast to the examples with push sources, most
+of the work here happens on-demand in the {{UnderlyingSource/pull|pull()}} function, and not at
+startup time in the {{UnderlyingSource/start|start()}} function.
+
+
+const fs = require("fs").promises;
+const CHUNK_SIZE = 1024;
+
+function makeReadableFileStream(filename) {
+ let fileHandle;
+ let position = 0;
+
+ return new ReadableStream({
+ async start() {
+ fileHandle = await fs.open(filename, "r");
+ },
+
+ async pull(controller) {
+ const buffer = new ArrayBuffer(CHUNK_SIZE);
+
+ const { bytesRead } = await fileHandle.read(buffer, 0, CHUNK_SIZE, position);
+ if (bytesRead === 0) {
+ await fileHandle.close();
+ controller.close();
+ } else {
+ position += bytesRead;
+ controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
+ }
+ },
- return new ReadableStream({
- type: "bytes",
+ cancel() {
+ return fileHandle.close();
+ }
+ });
+}
+
- start() {
- return fs.open(filename, "r").then(result => {
- fd = result;
- });
- },
+We can then create and use readable streams for files just as we could before for sockets.
- pull(controller) {
- // Even when the consumer is using the default reader, the auto-allocation
- // feature allocates a buffer and passes it to us via byobRequest.
- const v = controller.byobRequest.view;
+A readable byte stream with an underlying pull source
- return fs.read(fd, v.buffer, v.byteOffset, v.byteLength, position).then(bytesRead => {
- if (bytesRead === 0) {
- return fs.close(fd).then(() => controller.close());
- } else {
- position += bytesRead;
- controller.byobRequest.respond(bytesRead);
- }
- });
- },
+The following function returns [=readable byte streams=] that allow efficient zero-copy reading of
+files, again using the Node.js file system API.
+Instead of using a predetermined chunk size of 1024, it attempts to fill the developer-supplied
+buffer, allowing full control.
+
+
+const fs = require("fs").promises;
+const DEFAULT_CHUNK_SIZE = 1024;
+
+function makeReadableByteFileStream(filename) {
+ let fileHandle;
+ let position = 0;
+
+ return new ReadableStream({
+ type: "bytes",
+
+ async start() {
+ fileHandle = await fs.open(filename, "r");
+ },
+
+ pull(controller) {
+ // Even when the consumer is using the default reader, the auto-allocation
+ // feature allocates a buffer and passes it to us via byobRequest.
+ const v = controller.byobRequest.view;
+
+ const { bytesRead } = await fileHandle.read(v.buffer, v.byteOffset, v.byteLength);
+ if (bytesRead === 0) {
+ await fileHandle.close();
+ controller.close();
+ } else {
+ position += bytesRead;
+ controller.byobRequest.respond(bytesRead);
+ }
+ },
- cancel() {
- return fs.close(fd);
- },
+ cancel() {
+ return fs.close(fd);
+ },
- autoAllocateChunkSize: DEFAULT_CHUNK_SIZE
- });
- }
-
+ autoAllocateChunkSize: DEFAULT_CHUNK_SIZE
+ });
+}
+
-With this in hand, we can create and use BYOB readers for the returned {{ReadableStream}}. But we can
-also create default readers, using them in the same simple and generic manner as usual. The adaptation between
-the low-level byte tracking of the underlying byte source shown here, and the higher-level chunk-based
-consumption of a default reader, is all taken care of automatically by the streams implementation. The
-auto-allocation feature, via the autoAllocateChunkSize
option, even allows
-us to write less code, compared to the manual branching in [[#example-rbs-push]].
+With this in hand, we can create and use [=BYOB readers=] for the returned {{ReadableStream}}. But
+we can also create [=default readers=], using them in the same simple and generic manner as usual.
+The adaptation between the low-level byte tracking of the [=underlying byte source=] shown here,
+and the higher-level chunk-based consumption of a [=default reader=], is all taken care of
+automatically by the streams implementation. The auto-allocation feature, via the
+{{UnderlyingSource/autoAllocateChunkSize}} option, even allows us to write less code, compared to
+the manual branching in [[#example-rbs-push]].
A writable stream with no backpressure or success signals
-The following function returns a writable stream that wraps a {{WebSocket}} [[HTML]]. Web sockets do not provide
-any way to tell when a given chunk of data has been successfully sent (without awkward polling of
-{{WebSocket/bufferedAmount}}, which we leave as an exercise to the reader). As such, this writable stream has no ability
-to communicate accurate backpressure signals or write success/failure to its producers. That is, the
-promises returned by its writer's {{WritableStreamDefaultWriter/write()}} method and
+The following function returns a [=writable stream=] that wraps a {{WebSocket}} [[HTML]]. Web
+sockets do not provide any way to tell when a given chunk of data has been successfully sent
+(without awkward polling of {{WebSocket/bufferedAmount}}, which we leave as an exercise to the
+reader). As such, this writable stream has no ability to communicate accurate [=backpressure=]
+signals or write success/failure to its [=producers=]. That is, the promises returned by its
+[=writer=]'s {{WritableStreamDefaultWriter/write()}} method and
{{WritableStreamDefaultWriter/ready}} getter will always fulfill immediately.
-
- function makeWritableWebSocketStream(url, protocols) {
- const ws = new WebSocket(url, protocols);
-
- return new WritableStream({
- start(controller) {
- ws.onerror = () => {
- controller.error(new Error("The WebSocket errored!"));
- ws.onclose = null;
- };
- ws.onclose = () => controller.error(new Error("The server closed the connection unexpectedly!"));
- return new Promise(resolve => ws.onopen = resolve);
- },
-
- write(chunk) {
- ws.send(chunk);
- // Return immediately, since the web socket gives us no easy way to tell
- // when the write completes.
- },
-
- close() {
- return closeWS(1000);
- },
-
- abort(reason) {
- return closeWS(4000, reason && reason.message);
- },
- });
-
- function closeWS(code, reasonString) {
- return new Promise((resolve, reject) => {
- ws.onclose = e => {
- if (e.wasClean) {
- resolve();
- } else {
- reject(new Error("The connection was not closed cleanly"));
- }
- };
- ws.close(code, reasonString);
- });
- }
- }
-
-
-We can then use this function to create writable streams for a web socket, and pipe an arbitrary readable stream to it:
+
+function makeWritableWebSocketStream(url, protocols) {
+ const ws = new WebSocket(url, protocols);
-
- const webSocketStream = makeWritableWebSocketStream("wss://example.com:443/", "protocol");
-
- readableStream.pipeTo(webSocketStream)
- .then(() => console.log("All data successfully written!"))
- .catch(e => console.error("Something went wrong!", e));
-
+ return new WritableStream({
+ start(controller) {
+ ws.onerror = () => {
+ controller.error(new Error("The WebSocket errored!"));
+ ws.onclose = null;
+ };
+ ws.onclose = () => controller.error(new Error("The server closed the connection unexpectedly!"));
+ return new Promise(resolve => ws.onopen = resolve);
+ },
-See the earlier note about this style of wrapping web
-sockets into streams.
+ write(chunk) {
+ ws.send(chunk);
+ // Return immediately, since the web socket gives us no easy way to tell
+ // when the write completes.
+ },
-A writable stream with backpressure and success signals
+ close() {
+ return closeWS(1000);
+ },
-The following function returns writable streams that wrap portions of the Node.js file system API (which themselves map fairly directly to C's
-fopen
, fwrite
, and fclose
trio). Since the API we are wrapping provides a way to
-tell when a given write succeeds, this stream will be able to communicate backpressure signals as well as whether
-an individual write succeeded or failed.
+ abort(reason) {
+ return closeWS(4000, reason && reason.message);
+ },
+ });
-
- const fs = require("pr/fs"); // https://github.com/jden/pr
+ function closeWS(code, reasonString) {
+ return new Promise((resolve, reject) => {
+ ws.onclose = e => {
+ if (e.wasClean) {
+ resolve();
+ } else {
+ reject(new Error("The connection was not closed cleanly"));
+ }
+ };
+ ws.close(code, reasonString);
+ });
+ }
+}
+
- function makeWritableFileStream(filename) {
- let fd;
+We can then use this function to create writable streams for a web socket, and pipe an arbitrary
+readable stream to it:
- return new WritableStream({
- start() {
- return fs.open(filename, "w").then(result => {
- fd = result;
- });
- },
+
+const webSocketStream = makeWritableWebSocketStream("wss://example.com:443/", "protocol");
- write(chunk) {
- return fs.write(fd, chunk, 0, chunk.length);
- },
+readableStream.pipeTo(webSocketStream)
+ .then(() => console.log("All data successfully written!"))
+ .catch(e => console.error("Something went wrong!", e));
+
- close() {
- return fs.close(fd);
- },
+See the earlier note about this
+style of wrapping web sockets into streams.
- abort() {
- return fs.close(fd);
- }
- });
- }
-
+
A writable stream with backpressure and success signals
-We can then use this function to create a writable stream for a file, and write individual chunks of data to it:
+The following function returns [=writable streams=] that wrap portions of the Node.js file system API (which themselves map fairly
+directly to C's fopen
, fwrite
, and fclose
trio). Since the
+API we are wrapping provides a way to tell when a given write succeeds, this stream will be able to
+communicate [=backpressure=] signals as well as whether an individual write succeeded or failed.
-
- const fileStream = makeWritableFileStream("/example/path/on/fs.txt");
- const writer = fileStream.getWriter();
+
+const fs = require("fs").promises;
- writer.write("To stream, or not to stream\n");
- writer.write("That is the question\n");
+function makeWritableFileStream(filename) {
+ let fileHandle;
- writer.close()
- .then(() => console.log("chunks written and stream closed successfully!"))
- .catch(e => console.error(e));
-
+ return new WritableStream({
+ async start() {
+ fileHandle = await fs.open(filename, "w");
+ },
-Note that if a particular call to fs.write
takes a longer time, the returned promise will fulfill later.
-In the meantime, additional writes can be queued up, which are stored in the stream's internal queue. The accumulation
-of chunks in this queue can change the stream to return a pending promise from the {{WritableStreamDefaultWriter/ready}}
-getter, which is a signal to producers that they would benefit from backing off and stopping writing, if
-possible.
+ write(chunk) {
+ return fileHandle.write(chunk, 0, chunk.length);
+ },
-The way in which the writable stream queues up writes is especially important in this case, since as stated in
-the documentation for
-fs.write
, "it is unsafe to use fs.write
multiple times on the same file without waiting
-for the [promise]." But we don't have to worry about that when writing the makeWritableFileStream
-function, since the stream implementation guarantees that the underlying sink's {{underlying sink/write()}}
-method will not be called until any promises returned by previous calls have fulfilled!
+ close() {
+ return fs.close(fd);
+ },
-A { readable, writable } stream pair wrapping the same underlying resource
+ abort() {
+ return fs.close(fd);
+ }
+ });
+}
+
+
+We can then use this function to create a writable stream for a file, and write individual
+[=chunks=] of data to it:
+
+
+const fileStream = makeWritableFileStream("/example/path/on/fs.txt");
+const writer = fileStream.getWriter();
+
+writer.write("To stream, or not to stream\n");
+writer.write("That is the question\n");
+
+writer.close()
+ .then(() => console.log("chunks written and stream closed successfully!"))
+ .catch(e => console.error(e));
+
+
+Note that if a particular call to fileHandle.write
takes a longer time, the returned
+promise will fulfill later. In the meantime, additional writes can be queued up, which are stored
+in the stream's internal queue. The accumulation of chunks in this queue can change the stream to
+return a pending promise from the {{WritableStreamDefaultWriter/ready}} getter, which is a signal
+to [=producers=] that they would benefit from backing off and stopping writing, if possible.
+
+The way in which the writable stream queues up writes is especially important in this case, since
+as stated in the
+documentation for fileHandle.write
, "it is unsafe to use
+filehandle.write
multiple times on the same file without waiting for the promise." But
+we don't have to worry about that when writing the makeWritableFileStream
function,
+since the stream implementation guarantees that the [=underlying sink=]'s
+{{UnderlyingSink/write|write()}} method will not be called until any promises returned by previous
+calls have fulfilled!
+
+A { readable, writable } stream pair wrapping the same underlying
+resource
The following function returns an object of the form { readable, writable }
, with the
-readable
property containing a readable stream and the writable
property containing a
-writable stream, where both streams wrap the same underlying web socket resource. In essence, this combines
-[[#example-rs-push-no-backpressure]] and [[#example-ws-no-backpressure]].
-
-While doing so, it illustrates how you can use JavaScript classes to create reusable underlying sink and underlying
-source abstractions.
-
-
- function streamifyWebSocket(url, protocol) {
- const ws = new WebSocket(url, protocols);
- ws.binaryType = "arraybuffer";
-
- return {
- readable: new ReadableStream(new WebSocketSource(ws)),
- writable: new WritableStream(new WebSocketSink(ws))
- };
+readable
property containing a readable stream and the writable
property
+containing a writable stream, where both streams wrap the same underlying web socket resource. In
+essence, this combines [[#example-rs-push-no-backpressure]] and [[#example-ws-no-backpressure]].
+
+While doing so, it illustrates how you can use JavaScript classes to create reusable underlying
+sink and underlying source abstractions.
+
+
+function streamifyWebSocket(url, protocol) {
+ const ws = new WebSocket(url, protocols);
+ ws.binaryType = "arraybuffer";
+
+ return {
+ readable: new ReadableStream(new WebSocketSource(ws)),
+ writable: new WritableStream(new WebSocketSink(ws))
+ };
+}
+
+class WebSocketSource {
+ constructor(ws) {
+ this._ws = ws;
}
- class WebSocketSource {
- constructor(ws) {
- this._ws = ws;
- }
-
- start(controller) {
- this._ws.onmessage = event => controller.enqueue(event.data);
- this._ws.onclose = () => controller.close();
+ start(controller) {
+ this._ws.onmessage = event => controller.enqueue(event.data);
+ this._ws.onclose = () => controller.close();
- this._ws.addEventListener("error", () => {
- controller.error(new Error("The WebSocket errored!"));
- });
- }
+ this._ws.addEventListener("error", () => {
+ controller.error(new Error("The WebSocket errored!"));
+ });
+ }
- cancel() {
- this._ws.close();
- }
+ cancel() {
+ this._ws.close();
}
+}
- class WebSocketSink {
- constructor(ws) {
- this._ws = ws;
- }
+class WebSocketSink {
+ constructor(ws) {
+ this._ws = ws;
+ }
- start(controller) {
- this._ws.onclose = () => controller.error(new Error("The server closed the connection unexpectedly!"));
- this._ws.addEventListener("error", () => {
- controller.error(new Error("The WebSocket errored!"));
- this._ws.onclose = null;
- });
+ start(controller) {
+ this._ws.onclose = () => controller.error(new Error("The server closed the connection unexpectedly!"));
+ this._ws.addEventListener("error", () => {
+ controller.error(new Error("The WebSocket errored!"));
+ this._ws.onclose = null;
+ });
- return new Promise(resolve => this._ws.onopen = resolve);
- }
+ return new Promise(resolve => this._ws.onopen = resolve);
+ }
- write(chunk) {
- this._ws.send(chunk);
- }
+ write(chunk) {
+ this._ws.send(chunk);
+ }
- close() {
- return this._closeWS(1000);
- }
+ close() {
+ return this._closeWS(1000);
+ }
- abort(reason) {
- return this._closeWS(4000, reason && reason.message);
- }
+ abort(reason) {
+ return this._closeWS(4000, reason && reason.message);
+ }
- _closeWS(code, reasonString) {
- return new Promise((resolve, reject) => {
- this._ws.onclose = e => {
- if (e.wasClean) {
- resolve();
- } else {
- reject(new Error("The connection was not closed cleanly"));
- }
- };
- this._ws.close(code, reasonString);
- });
- }
+ _closeWS(code, reasonString) {
+ return new Promise((resolve, reject) => {
+ this._ws.onclose = e => {
+ if (e.wasClean) {
+ resolve();
+ } else {
+ reject(new Error("The connection was not closed cleanly"));
+ }
+ };
+ this._ws.close(code, reasonString);
+ });
}
-
+}
+
-We can then use the objects created by this function to communicate with a remote web socket, using the standard stream
-APIs:
+We can then use the objects created by this function to communicate with a remote web socket, using
+the standard stream APIs:
-
- const streamyWS = streamifyWebSocket("wss://example.com:443/", "protocol");
- const writer = streamyWS.writable.getWriter();
- const reader = streamyWS.readable.getReader();
+
+const streamyWS = streamifyWebSocket("wss://example.com:443/", "protocol");
+const writer = streamyWS.writable.getWriter();
+const reader = streamyWS.readable.getReader();
- writer.write("Hello");
- writer.write("web socket!");
+writer.write("Hello");
+writer.write("web socket!");
- reader.read().then(({ value, done }) => {
- console.log("The web socket says: ", value);
- });
-
+reader.read().then(({ value, done }) => {
+ console.log("The web socket says: ", value);
+});
+
-Note how in this setup canceling the readable
side will implicitly close the writable
side,
-and similarly, closing or aborting the writable
side will implicitly close the readable
side.
+Note how in this setup canceling the readable
side will implicitly close the
+writable
side, and similarly, closing or aborting the writable
side will
+implicitly close the readable
side.
-See the earlier note about this style of wrapping web
-sockets into streams.
+See the earlier note about this
+style of wrapping web sockets into streams.
A transform stream that replaces template tags
-It's often useful to substitute tags with variables on a stream of data, where the parts that need to be replaced are
-small compared to the overall data size. This example presents a simple way to do that. It maps strings to strings,
-transforming a template like "Time: \{{time}} Message: \{{message}}"
to "Time: 15:36 Message:
-hello"
assuming that { time: "15:36", message: "hello" }
was passed in the
-substitutions
parameter to LipFuzzTransformer
.
-
-This example also demonstrates one way to deal with a situation where a chunk contains partial data that cannot be
-transformed until more data is received. In this case, a partial template tag will be accumulated in the
-partialChunk
instance variable until either the end of the tag is found or the end of the stream is
-reached.
-
-
- class LipFuzzTransformer {
- constructor(substitutions) {
- this.substitutions = substitutions;
- this.partialChunk = "";
- this.lastIndex = undefined;
- }
+It's often useful to substitute tags with variables on a stream of data, where the parts that need
+to be replaced are small compared to the overall data size. This example presents a simple way to
+do that. It maps strings to strings, transforming a template like "Time: \{{time}} Message:
+\{{message}}"
to "Time: 15:36 Message: hello"
assuming that { time:
+"15:36", message: "hello" }
was passed in the substitutions
parameter to
+LipFuzzTransformer
.
+
+This example also demonstrates one way to deal with a situation where a chunk contains partial data
+that cannot be transformed until more data is received. In this case, a partial template tag will
+be accumulated in the partialChunk
property until either the end of the tag is found or
+the end of the stream is reached.
+
+
+class LipFuzzTransformer {
+ constructor(substitutions) {
+ this.substitutions = substitutions;
+ this.partialChunk = "";
+ this.lastIndex = undefined;
+ }
- transform(chunk, controller) {
- chunk = this.partialChunk + chunk;
- this.partialChunk = "";
- // lastIndex is the index of the first character after the last substitution.
- this.lastIndex = 0;
- chunk = chunk.replace(/\{\{([a-zA-Z0-9_-]+)\}\}/g, this.replaceTag.bind(this));
- // Regular expression for an incomplete template at the end of a string.
- const partialAtEndRegexp = /\{(\{([a-zA-Z0-9_-]+(\})?)?)?$/g;
- // Avoid looking at any characters that have already been substituted.
- partialAtEndRegexp.lastIndex = this.lastIndex;
- this.lastIndex = undefined;
- const match = partialAtEndRegexp.exec(chunk);
- if (match) {
- this.partialChunk = chunk.substring(match.index);
- chunk = chunk.substring(0, match.index);
- }
- controller.enqueue(chunk);
+ transform(chunk, controller) {
+ chunk = this.partialChunk + chunk;
+ this.partialChunk = "";
+ // lastIndex is the index of the first character after the last substitution.
+ this.lastIndex = 0;
+ chunk = chunk.replace(/\{\{([a-zA-Z0-9_-]+)\}\}/g, this.replaceTag.bind(this));
+ // Regular expression for an incomplete template at the end of a string.
+ const partialAtEndRegexp = /\{(\{([a-zA-Z0-9_-]+(\})?)?)?$/g;
+ // Avoid looking at any characters that have already been substituted.
+ partialAtEndRegexp.lastIndex = this.lastIndex;
+ this.lastIndex = undefined;
+ const match = partialAtEndRegexp.exec(chunk);
+ if (match) {
+ this.partialChunk = chunk.substring(match.index);
+ chunk = chunk.substring(0, match.index);
}
+ controller.enqueue(chunk);
+ }
- flush(controller) {
- if (this.partialChunk.length > 0) {
- controller.enqueue(this.partialChunk);
- }
+ flush(controller) {
+ if (this.partialChunk.length > 0) {
+ controller.enqueue(this.partialChunk);
}
+ }
- replaceTag(match, p1, offset) {
- let replacement = this.substitutions[p1];
- if (replacement === undefined) {
- replacement = "";
- }
- this.lastIndex = offset + replacement.length;
- return replacement;
+ replaceTag(match, p1, offset) {
+ let replacement = this.substitutions[p1];
+ if (replacement === undefined) {
+ replacement = "";
}
+ this.lastIndex = offset + replacement.length;
+ return replacement;
}
-
+}
+
-In this case we define the transformer to be passed to the {{TransformStream}} constructor as a class. This is
-useful when there is instance data to track.
+In this case we define the [=transformer=] to be passed to the {{TransformStream}} constructor as a
+class. This is useful when there is instance data to track.
The class would be used in code like:
-
- const data = { userName, displayName, icon, date };
- const ts = new TransformStream(new LipFuzzTransformer(data));
-
- fetchEvent.respondWith(
- fetch(fetchEvent.request.url).then(response => {
- const transformedBody = response.body
- // Decode the binary-encoded response to string
- .pipeThrough(new TextDecoderStream())
- // Apply the LipFuzzTransformer
- .pipeThrough(ts)
- // Encode the transformed string
- .pipeThrough(new TextEncoderStream());
- return new Response(transformedBody);
- })
- );
-
-
-For simplicity, LipFuzzTransformer
performs unescaped text substitutions. In real
-applications, a template system that performs context-aware escaping is good practice for security and robustness.
+
+const data = { userName, displayName, icon, date };
+const ts = new TransformStream(new LipFuzzTransformer(data));
+
+fetchEvent.respondWith(
+ fetch(fetchEvent.request.url).then(response => {
+ const transformedBody = response.body
+ // Decode the binary-encoded response to string
+ .pipeThrough(new TextDecoderStream())
+ // Apply the LipFuzzTransformer
+ .pipeThrough(ts)
+ // Encode the transformed string
+ .pipeThrough(new TextEncoderStream());
+ return new Response(transformedBody);
+ })
+);
+
+
+For simplicity, LipFuzzTransformer
performs unescaped text
+substitutions. In real applications, a template system that performs context-aware escaping is good
+practice for security and robustness.
A transform stream created from a sync mapper function
-The following function allows creating new {{TransformStream}} instances from synchronous "mapper" functions, of the
-type you would normally pass to {{Array.prototype/map|Array.prototype.map}}. It demonstrates that the API is concise
-even for trivial transforms.
+The following function allows creating new {{TransformStream}} instances from synchronous "mapper"
+functions, of the type you would normally pass to {{Array.prototype/map|Array.prototype.map}}. It
+demonstrates that the API is concise even for trivial transforms.
-
- function mapperTransformStream(mapperFunction) {
- return new TransformStream({
- transform(chunk, controller) {
- controller.enqueue(mapperFunction(chunk));
- }
- });
- }
-
+
+function mapperTransformStream(mapperFunction) {
+ return new TransformStream({
+ transform(chunk, controller) {
+ controller.enqueue(mapperFunction(chunk));
+ }
+ });
+}
+
This function can then be used to create a {{TransformStream}} that uppercases all its inputs:
-
- const ts = mapperTransformStream(chunk => chunk.toUpperCase());
- const writer = ts.writable.getWriter();
- const reader = ts.readable.getReader();
+
+const ts = mapperTransformStream(chunk => chunk.toUpperCase());
+const writer = ts.writable.getWriter();
+const reader = ts.readable.getReader();
- writer.write("No need to shout");
+writer.write("No need to shout");
- // Logs "NO NEED TO SHOUT":
- reader.read().then(({ value }) => console.log(value));
-
+// Logs "NO NEED TO SHOUT":
+reader.read().then(({ value }) => console.log(value));
+
-Although a synchronous transform never causes backpressure itself, it will only transform chunks as long as there is no
-backpressure, so resources will not be wasted.
+Although a synchronous transform never causes backpressure itself, it will only transform chunks as
+long as there is no backpressure, so resources will not be wasted.
Exceptions error the stream in a natural way:
-
- const ts = mapperTransformStream(chunk => JSON.parse(chunk));
- const writer = ts.writable.getWriter();
- const reader = ts.readable.getReader();
+
+const ts = mapperTransformStream(chunk => JSON.parse(chunk));
+const writer = ts.writable.getWriter();
+const reader = ts.readable.getReader();
- writer.write("[1, ");
-
- // Logs a SyntaxError, twice:
- reader.read().catch(e => console.error(e));
- writer.write("{}").catch(e => console.error(e));
-
-
-Conventions
-
-This specification depends on the Infra Standard. [[!INFRA]]
+writer.write("[1, ");
-This specification uses algorithm conventions very similar to those of [[!ECMASCRIPT]], whose rules should be used to
-interpret it (apart from the exceptions enumerated below). In particular, the objects specified here should be treated
-as built-in objects. For example,
-their name
and length
properties are derived as described by that specification, as are the
-default property descriptor values and the treatment of missing, undefined , or surplus arguments.
-
-We also depart from the [[!ECMASCRIPT]] conventions in the following ways, mostly for brevity. It is hoped (and vaguely
-planned) that the conventions of ECMAScript itself will evolve in these ways.
-
-
- - We prefix section headings with
new
to indicate they are defining constructors; when doing so, we
- assume that NewTarget will be checked before the algorithm starts.
- - We use the default argument notation
= {}
in a couple of cases, meaning that before the algorithm
- starts, undefined (including the implicit undefined when no argument is
- provided) is instead treated as a new object created as if by ObjectCreate(%ObjectPrototype%). (This object may then
- be destructured, if combined with the below destructuring convention.)
- - We use destructuring notation in function and method declarations, and assume that DestructuringAssignmentEvaluation was performed appropriately before the algorithm starts.
-
- We use "
this " instead of "this value".
-
-
-It's also worth noting that, as in [[!ECMASCRIPT]], all numbers are represented as double-precision floating point
-values, and all arithmetic operations performed on them must be done in the standard way for such values.
+// Logs a SyntaxError, twice:
+reader.read().catch(e => console.error(e));
+writer.write("{}").catch(e => console.error(e));
+
Acknowledgments
diff --git a/local-watch.js b/local-watch.js
deleted file mode 100644
index 8c8924789..000000000
--- a/local-watch.js
+++ /dev/null
@@ -1,83 +0,0 @@
-'use strict';
-const fs = require('fs');
-const childProcess = require('child_process');
-const promiseDebounce = require('promise-debounce');
-const emuAlgify = require('emu-algify');
-
-const INPUT = 'index.bs';
-
-let fsWatcher;
-
-const build = promiseDebounce(() => {
- log('Building...');
-
- try {
- childProcess.execSync(
- `bikeshed spec ${INPUT} index.html.postbs --md-Text-Macro="SNAPSHOT-LINK "`,
- { encoding: 'utf-8', stdio: 'inherit' }
- );
- log('(bikeshed done)');
- } catch (e) {
- error('Error executing bikeshed:\n');
- console.error(e.stdout);
- }
-
- const input = fs.readFileSync('index.html.postbs', { encoding: 'utf-8' });
- fs.unlinkSync('index.html.postbs');
-
- return emuAlgify(input, { throwingIndicators: true })
- .then(output => {
- fs.writeFileSync('index.html.new', output);
- fs.renameSync('index.html.new', 'index.html');
- log('Build complete');
- })
- .catch(err => {
- error('Error executing ecmarkupify:\n');
- console.error(err);
- }
- );
-});
-
-function onChange(eventType, filename) {
- log(`Saw ${eventType} event with filename '${filename}'`);
- // Restart the watch in case the file has been renamed or deleted. This fixes an issue where the file stopped being
- // watched when a different branch was checked out.
- tryWatch();
-}
-
-// If index.bs exists, start watching it and run a build. Otherwise retry with a truncated exponential delay until it
-// starts to exist.
-function tryWatch(delay) {
- if (fsWatcher !== undefined) {
- fsWatcher.close();
- fsWatcher = undefined;
- }
- try {
- fsWatcher = fs.watch(INPUT, onChange);
- build();
- } catch (e) {
- if (e.code === 'ENOENT') {
- log(`${INPUT} not there right now. Waiting a bit.`);
- if (delay === undefined) {
- delay = 100;
- }
- delay *= 2;
- if (delay > 20000) {
- delay = 20000;
- }
- setTimeout(tryWatch, delay, delay);
- } else {
- throw e;
- }
- }
-}
-
-tryWatch();
-
-function log(s) {
- console.log(`[${(new Date()).toISOString()}] ${s}`);
-}
-
-function error(s) {
- console.error(`[${(new Date()).toISOString()}] ${s}`);
-}
diff --git a/package.json b/package.json
deleted file mode 100644
index b49f7348f..000000000
--- a/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "private": true,
- "description": "Build process supporting stuff for WHATWG Streams Standard document",
- "scripts": {
- "local-watch": "node ./local-watch.js"
- },
- "devDependencies": {
- "emu-algify": "^2.2.0",
- "promise-debounce": "^1.0.0"
- }
-}