Skip to content

Commit 9fcffb4

Browse files
committed
fs: add signal option to fs.stat()
1 parent 77f88b9 commit 9fcffb4

File tree

3 files changed

+71
-8
lines changed

3 files changed

+71
-8
lines changed

doc/api/fs.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -622,15 +622,17 @@ Read from a file and write to an array of {ArrayBufferView}s
622622
<!-- YAML
623623
added: v10.0.0
624624
changes:
625+
- version: REPLACEME
626+
pr-url: https://github.com/nodejs/node/pull/57775
627+
description: Now accepts an additional `signal` property to allow aborting the operation.
625628
- version: v10.5.0
626629
pr-url: https://github.com/nodejs/node/pull/20220
627-
description: Accepts an additional `options` object to specify whether
628-
the numeric values returned should be bigint.
630+
description: Accepts an additional `options` object to specify whether the numeric values returned should be bigint.
629631
-->
630632
631633
* `options` {Object}
632-
* `bigint` {boolean} Whether the numeric values in the returned
633-
{fs.Stats} object should be `bigint`. **Default:** `false`.
634+
* `bigint` {boolean} Whether the numeric values in the returned {fs.Stats} object should be `bigint`. **Default:** `false`.
635+
* `signal` {AbortSignal} An AbortSignal to cancel the operation. **Default:** `undefined`.
634636
* Returns: {Promise} Fulfills with an {fs.Stats} for the file.
635637
636638
#### `filehandle.sync()`

lib/fs.js

+40-4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const {
7373
codes: {
7474
ERR_ACCESS_DENIED,
7575
ERR_FS_FILE_TOO_LARGE,
76+
ERR_INVALID_ARG_TYPE,
7677
ERR_INVALID_ARG_VALUE,
7778
},
7879
} = require('internal/errors');
@@ -1620,22 +1621,57 @@ function lstat(path, options = { bigint: false }, callback) {
16201621
/**
16211622
* Asynchronously gets the stats of a file.
16221623
* @param {string | Buffer | URL} path
1623-
* @param {{ bigint?: boolean; }} [options]
1624+
* @param {{ bigint?: boolean, signal?: AbortSignal }} [options]
16241625
* @param {(
16251626
* err?: Error,
16261627
* stats?: Stats
16271628
* ) => any} callback
16281629
* @returns {void}
16291630
*/
1630-
function stat(path, options = { bigint: false }, callback) {
1631+
function stat(path, options = { __proto__: null, bigint: false, signal: undefined }, callback) {
16311632
if (typeof options === 'function') {
16321633
callback = options;
16331634
options = kEmptyObject;
1635+
} else if (options === null || typeof options !== 'object') {
1636+
options = kEmptyObject;
1637+
} else {
1638+
options = getOptions(options, { __proto__: null, bigint: false, signal: undefined });
1639+
}
1640+
1641+
if (typeof callback !== 'function') {
1642+
throw new ERR_INVALID_ARG_TYPE('callback', 'Function', callback);
16341643
}
1635-
callback = makeStatsCallback(callback);
16361644

1645+
const originalCallback = callback;
16371646
const req = new FSReqCallback(options.bigint);
1638-
req.oncomplete = callback;
1647+
1648+
if (options.signal?.aborted) {
1649+
const abortErr = new AbortError('The operation was aborted', { __proto__: null, cause: options.signal.reason });
1650+
return process.nextTick(() => originalCallback(abortErr));
1651+
}
1652+
1653+
let aborted = false;
1654+
const onAbort = () => {
1655+
aborted = true;
1656+
originalCallback(new AbortError(undefined, { __proto__: null, cause: options.signal.reason }));
1657+
};
1658+
1659+
if (options.signal) {
1660+
options.signal.addEventListener('abort', onAbort, { __proto__: null, once: true });
1661+
}
1662+
1663+
req.oncomplete = (err, stats) => {
1664+
if (options.signal) {
1665+
options.signal.removeEventListener('abort', onAbort);
1666+
}
1667+
1668+
if (aborted) return;
1669+
1670+
const wrappedCallback = makeStatsCallback(originalCallback);
1671+
1672+
wrappedCallback(err, stats);
1673+
};
1674+
16391675
binding.stat(getValidatedPath(path), options.bigint, req);
16401676
}
16411677

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
require('../common');
4+
const test = require('node:test');
5+
const assert = require('node:assert');
6+
const fs = require('node:fs');
7+
8+
const filePath = './temp.txt';
9+
fs.writeFileSync(filePath, 'Test');
10+
11+
test('fs.stat should throw AbortError when abort signal is triggered', async () => {
12+
const signal = AbortSignal.abort();
13+
14+
const { promise, resolve, reject } = Promise.withResolvers();
15+
fs.stat(filePath, { signal }, (err, stats) => {
16+
if (err) {
17+
return reject(err);
18+
}
19+
resolve(stats);
20+
});
21+
22+
await assert.rejects(promise, { name: 'AbortError' });
23+
24+
fs.unlinkSync(filePath);
25+
});

0 commit comments

Comments
 (0)