diff --git a/doc/api/fs.md b/doc/api/fs.md index 173c0d52ba95d6..374e51434d78a2 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -267,6 +267,7 @@ added: v16.11.0 * `start` {integer} * `end` {integer} **Default:** `Infinity` * `highWaterMark` {integer} **Default:** `64 * 1024` + * `signal` {AbortSignal|undefined} **Default:** `undefined` * Returns: {fs.ReadStream} Unlike the 16 KiB default `highWaterMark` for a {stream.Readable}, the stream diff --git a/test/parallel/test-fs-read-stream-file-handle.js b/test/parallel/test-fs-read-stream-file-handle.js index b7abb5df575569..eb54ffe9210bab 100644 --- a/test/parallel/test-fs-read-stream-file-handle.js +++ b/test/parallel/test-fs-read-stream-file-handle.js @@ -80,3 +80,75 @@ fs.promises.open(file, 'r').then((handle) => { assert.strictEqual(output, input); })); }).then(common.mustCall()); + +// AbortSignal option test +fs.promises.open(file, 'r').then((handle) => { + const controller = new AbortController(); + const { signal } = controller; + const stream = handle.createReadStream({ signal }); + + stream.on('data', common.mustNotCall()); + stream.on('end', common.mustNotCall()); + + stream.on('error', common.mustCall((err) => { + assert.strictEqual(err.name, 'AbortError'); + })); + + stream.on('close', common.mustCall(() => { + handle.close(); + })); + + controller.abort(); +}).then(common.mustCall()); + +// Already-aborted signal test +fs.promises.open(file, 'r').then((handle) => { + const signal = AbortSignal.abort(); + const stream = handle.createReadStream({ signal }); + + stream.on('data', common.mustNotCall()); + stream.on('end', common.mustNotCall()); + + stream.on('error', common.mustCall((err) => { + assert.strictEqual(err.name, 'AbortError'); + })); + + stream.on('close', common.mustCall(() => { + handle.close(); + })); +}).then(common.mustCall()); + +// Invalid signal type test +fs.promises.open(file, 'r').then((handle) => { + for (const signal of [1, {}, [], '', null, NaN, 1n, () => {}, Symbol(), false, true]) { + assert.throws(() => { + handle.createReadStream({ signal }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + } + return handle.close(); +}).then(common.mustCall()); + +// Custom abort reason test +fs.promises.open(file, 'r').then((handle) => { + const controller = new AbortController(); + const { signal } = controller; + const reason = new Error('some silly abort reason'); + const stream = handle.createReadStream({ signal }); + + stream.on('data', common.mustNotCall()); + stream.on('end', common.mustNotCall()); + + stream.on('error', common.mustCall((err) => { + assert.strictEqual(err.name, 'AbortError'); + assert.strictEqual(err.cause, reason); + })); + + stream.on('close', common.mustCall(() => { + handle.close(); + })); + + controller.abort(reason); +}).then(common.mustCall());