diff --git a/lib/internal/webstreams/writablestream.js b/lib/internal/webstreams/writablestream.js index 1e9ca02cfe96..1843fd81bcda 100644 --- a/lib/internal/webstreams/writablestream.js +++ b/lib/internal/webstreams/writablestream.js @@ -928,6 +928,13 @@ function writableStreamFinishErroring(stream) { writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); return; } + // The closed promises are only rejected once the sink's abort algorithm + // settles, so materialize them here: deriving them from the 'errored' + // state within that window would hand out an already rejected promise. + stream[kState].closedPromise ??= PromiseWithResolvers(); + const writer = stream[kState].writer; + if (writer !== undefined) + writer[kState].close ??= PromiseWithResolvers(); PromisePrototypeThen( stream[kState].controller[kAbort](abortRequest.reason), () => { diff --git a/test/parallel/test-webstreams-writer-closed-abort-pending.js b/test/parallel/test-webstreams-writer-closed-abort-pending.js new file mode 100644 index 000000000000..b03ccbe3ef98 --- /dev/null +++ b/test/parallel/test-webstreams-writer-closed-abort-pending.js @@ -0,0 +1,42 @@ +'use strict'; + +// The writer's closed promise stays pending until the sink's abort() +// algorithm settles, even when it is first observed after the stream has +// already reached the 'errored' state. + +const common = require('../common'); +const assert = require('assert'); +const { setImmediate: immediate } = require('timers/promises'); +const { WritableStream } = require('stream/web'); + +async function main() { + const error = new Error('boom'); + const { promise: abortComplete, resolve: finishAbort } = Promise.withResolvers(); + const ws = new WritableStream({ + abort: common.mustCall((reason) => { + assert.strictEqual(reason, error); + return abortComplete; + }), + }); + const writer = ws.getWriter(); + const aborted = writer.abort(error); + + // Lets the stream finish erroring and call the sink's abort(). + await immediate(); + + let closedSettled = false; + const closed = writer.closed.catch(common.mustCall((reason) => { + closedSettled = true; + assert.strictEqual(reason, error); + })); + + await immediate(); + assert.strictEqual(closedSettled, false); + + finishAbort(); + await aborted; + await closed; + assert.strictEqual(closedSettled, true); +} + +main().then(common.mustCall());