Skip to content

Commit 547428b

Browse files
MattiasBuelensdomenic
authored andcommitted
Add close() method to WritableStream
Fixes #1007.
1 parent d26db0e commit 547428b

File tree

3 files changed

+88
-44
lines changed

3 files changed

+88
-44
lines changed

index.bs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3171,6 +3171,7 @@ like
31713171
get <a href="#ws-locked">locked</a>()
31723172

31733173
<a href="#ws-abort">abort</a>(reason)
3174+
<a href="#ws-close">close</a>()
31743175
<a href="#ws-get-writer">getWriter</a>()
31753176
}
31763177
</code></pre>
@@ -3385,6 +3386,25 @@ bridging the gap with non-promise-based APIs, as seen for example in [[#example-
33853386
1. Return ! WritableStreamAbort(*this*, _reason_).
33863387
</emu-alg>
33873388

3389+
<h5 id="ws-close" method for="WritableStream">close()</h5>
3390+
3391+
<div class="note">
3392+
The <code>close</code> method closes the stream. The <a>underlying sink</a> will finish processing any
3393+
previously-written <a>chunks</a>, before invoking its close behavior. During this time any further attempts to write
3394+
will fail (without erroring the stream).
3395+
3396+
The method returns a promise that is fulfilled with <emu-val>undefined</emu-val> if all remaining <a>chunks</a> are
3397+
successfully written and the stream successfully closes, or rejects if an error is encountered during this process.
3398+
</div>
3399+
3400+
<emu-alg>
3401+
1. If ! IsWritableStream(*this*) is *false*, return <a>a promise rejected with</a> a *TypeError* exception.
3402+
1. If ! IsWritableStreamLocked(*this*) is *true*, return <a>a promise rejected with</a> a *TypeError* exception.
3403+
1. If ! WritableStreamCloseQueuedOrInFlight(*this*) is *true*, return <a>a promise rejected with</a> a *TypeError*
3404+
exception.
3405+
1. Return ! WritableStreamClose(*this*).
3406+
</emu-alg>
3407+
33883408
<h5 id="ws-get-writer" method for="WritableStream">getWriter()</h5>
33893409

33903410
<div class="note">
@@ -3490,6 +3510,22 @@ writable stream is <a>locked to a writer</a>.
34903510
1. Return _promise_.
34913511
</emu-alg>
34923512

3513+
<h4 id="writable-stream-close" aoid="WritableStreamClose" nothrow>WritableStreamClose ( <var>stream</var> )</h4>
3514+
3515+
<emu-alg>
3516+
1. Let _state_ be _stream_.[[state]].
3517+
1. If _state_ is `"closed"` or `"errored"`, return <a>a promise rejected with</a> a *TypeError* exception.
3518+
1. Assert: _state_ is `"writable"` or `"erroring"`.
3519+
1. Assert: ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*.
3520+
1. Let _promise_ be <a>a new promise</a>.
3521+
1. Set _stream_.[[closeRequest]] to _promise_.
3522+
1. Let _writer_ be _stream_.[[writer]].
3523+
1. If _writer_ is not *undefined*, and _stream_.[[backpressure]] is *true*, and _state_ is `"writable"`,
3524+
<a>resolve</a> _writer_.[[readyPromise]] with *undefined*.
3525+
1. Perform ! WritableStreamDefaultControllerClose(_stream_.[[writableStreamController]]).
3526+
1. Return _promise_.
3527+
</emu-alg>
3528+
34933529
<h3 id="ws-abstract-ops-used-by-controllers">Writable stream abstract operations used by controllers</h3>
34943530

34953531
To allow future flexibility to add different writable stream behaviors (similar to the distinction between default
@@ -3853,12 +3889,8 @@ lt="WritableStreamDefaultWriter(stream)">new WritableStreamDefaultWriter(<var>st
38533889
<h5 id="default-writer-close" method for="WritableStreamDefaultWriter">close()</h5>
38543890

38553891
<div class="note">
3856-
The <code>close</code> method will close the associated writable stream. The <a>underlying sink</a> will finish
3857-
processing any previously-written <a>chunks</a>, before invoking its close behavior. During this time any further
3858-
attempts to write will fail (without erroring the stream).
3859-
3860-
The method returns a promise that is fulfilled with <emu-val>undefined</emu-val> if all remaining <a>chunks</a> are
3861-
successfully written and the stream successfully closes, or rejects if an error is encountered during this process.
3892+
If the writer is <a lt="active writer">active</a>, the <code>close</code> method behaves the same as that for the
3893+
associated stream. (Otherwise, it returns a rejected promise.)
38623894
</div>
38633895

38643896
<emu-alg>
@@ -3936,16 +3968,7 @@ nothrow>WritableStreamDefaultWriterClose ( <var>writer</var> )</h4>
39363968
<emu-alg>
39373969
1. Let _stream_ be _writer_.[[ownerWritableStream]].
39383970
1. Assert: _stream_ is not *undefined*.
3939-
1. Let _state_ be _stream_.[[state]].
3940-
1. If _state_ is `"closed"` or `"errored"`, return <a>a promise rejected with</a> a *TypeError* exception.
3941-
1. Assert: _state_ is `"writable"` or `"erroring"`.
3942-
1. Assert: ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*.
3943-
1. Let _promise_ be <a>a new promise</a>.
3944-
1. Set _stream_.[[closeRequest]] to _promise_.
3945-
1. If _stream_.[[backpressure]] is *true* and _state_ is `"writable"`, <a>resolve</a> _writer_.[[readyPromise]] with
3946-
*undefined*.
3947-
1. Perform ! WritableStreamDefaultControllerClose(_stream_.[[writableStreamController]]).
3948-
1. Return _promise_.
3971+
1. Return ! WritableStreamClose(_stream_).
39493972
</emu-alg>
39503973

39513974
<h4 id="writable-stream-default-writer-close-with-error-propagation" aoid="WritableStreamDefaultWriterCloseWithErrorPropagation"

reference-implementation/lib/writable-stream.js

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,22 @@ class WritableStream {
5555
return WritableStreamAbort(this, reason);
5656
}
5757

58+
close() {
59+
if (IsWritableStream(this) === false) {
60+
return promiseRejectedWith(streamBrandCheckException('close'));
61+
}
62+
63+
if (IsWritableStreamLocked(this) === true) {
64+
return promiseRejectedWith(new TypeError('Cannot close a stream that already has a writer'));
65+
}
66+
67+
if (WritableStreamCloseQueuedOrInFlight(this) === true) {
68+
return promiseRejectedWith(new TypeError('Cannot close an already-closing stream'));
69+
}
70+
71+
return WritableStreamClose(this);
72+
}
73+
5874
getWriter() {
5975
if (IsWritableStream(this) === false) {
6076
throw streamBrandCheckException('getWriter');
@@ -71,6 +87,7 @@ module.exports = {
7187
IsWritableStreamLocked,
7288
WritableStream,
7389
WritableStreamAbort,
90+
WritableStreamClose,
7491
WritableStreamDefaultControllerErrorIfNeeded,
7592
WritableStreamDefaultWriterCloseWithErrorPropagation,
7693
WritableStreamDefaultWriterRelease,
@@ -192,6 +209,35 @@ function WritableStreamAbort(stream, reason) {
192209
return promise;
193210
}
194211

212+
function WritableStreamClose(stream) {
213+
const state = stream._state;
214+
if (state === 'closed' || state === 'errored') {
215+
return promiseRejectedWith(new TypeError(
216+
`The stream (in ${state} state) is not in the writable state and cannot be closed`));
217+
}
218+
219+
assert(state === 'writable' || state === 'erroring');
220+
assert(WritableStreamCloseQueuedOrInFlight(stream) === false);
221+
222+
const promise = newPromise((resolve, reject) => {
223+
const closeRequest = {
224+
_resolve: resolve,
225+
_reject: reject
226+
};
227+
228+
stream._closeRequest = closeRequest;
229+
});
230+
231+
const writer = stream._writer;
232+
if (writer !== undefined && stream._backpressure === true && state === 'writable') {
233+
defaultWriterReadyPromiseResolve(writer);
234+
}
235+
236+
WritableStreamDefaultControllerClose(stream._writableStreamController);
237+
238+
return promise;
239+
}
240+
195241
// WritableStream API exposed for controllers.
196242

197243
function WritableStreamAddWriteRequest(stream) {
@@ -501,7 +547,7 @@ class WritableStreamDefaultWriter {
501547
}
502548

503549
if (WritableStreamCloseQueuedOrInFlight(stream) === true) {
504-
return promiseRejectedWith(new TypeError('cannot close an already-closing stream'));
550+
return promiseRejectedWith(new TypeError('Cannot close an already-closing stream'));
505551
}
506552

507553
return WritableStreamDefaultWriterClose(this);
@@ -565,34 +611,9 @@ function WritableStreamDefaultWriterClose(writer) {
565611

566612
assert(stream !== undefined);
567613

568-
const state = stream._state;
569-
if (state === 'closed' || state === 'errored') {
570-
return promiseRejectedWith(new TypeError(
571-
`The stream (in ${state} state) is not in the writable state and cannot be closed`));
572-
}
573-
574-
assert(state === 'writable' || state === 'erroring');
575-
assert(WritableStreamCloseQueuedOrInFlight(stream) === false);
576-
577-
const promise = newPromise((resolve, reject) => {
578-
const closeRequest = {
579-
_resolve: resolve,
580-
_reject: reject
581-
};
582-
583-
stream._closeRequest = closeRequest;
584-
});
585-
586-
if (stream._backpressure === true && state === 'writable') {
587-
defaultWriterReadyPromiseResolve(writer);
588-
}
589-
590-
WritableStreamDefaultControllerClose(stream._writableStreamController);
591-
592-
return promise;
614+
return WritableStreamClose(stream);
593615
}
594616

595-
596617
function WritableStreamDefaultWriterCloseWithErrorPropagation(writer) {
597618
const stream = writer._ownerWritableStream;
598619

0 commit comments

Comments
 (0)