Skip to content

Commit 665be46

Browse files
authored
Merge pull request #477 from mrmlnc/ISSUE-435_abort_signal
Support AbortSignal to abort the processing
2 parents bbc7d07 + c994937 commit 665be46

File tree

7 files changed

+84
-1
lines changed

7 files changed

+84
-1
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ This package provides methods for traversing the file system and returning pathn
3434
* [ignore](#ignore)
3535
* [suppressErrors](#suppresserrors)
3636
* [throwErrorOnBrokenSymbolicLink](#throwerroronbrokensymboliclink)
37+
* [signal](#signal)
3738
* [Output control](#output-control)
3839
* [absolute](#absolute)
3940
* [markDirectories](#markdirectories)
@@ -405,6 +406,15 @@ Throw an error when symbolic link is broken if `true` or safely return `lstat` c
405406

406407
> :book: This option has no effect on errors when reading the symbolic link directory.
407408
409+
#### signal
410+
411+
* Type: [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
412+
* Default: `undefined`
413+
414+
A signal to abort searching for entries on the file system. Works only with asynchronous methods for dynamic and static patterns.
415+
416+
> :book: The abort signal does not interrupt the operation instantly. After the abort signal, there will be a brief period during which the tail of unprocessed but already read directories will be processed. New directories will not be added to the queue for reading, entries found in processed directories will not be emitted. Think of it as a no-op loop because we do not know at what stage of processing the abort signal was triggered.
417+
408418
### Output control
409419

410420
#### absolute

src/index.spec.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,29 @@ describe('Package', () => {
108108

109109
assert.deepStrictEqual(actual, expected);
110110
});
111+
112+
it('should abort processing dynamic pattern with abort signal', async () => {
113+
const ac = new AbortController();
114+
115+
setTimeout(() => {
116+
ac.abort();
117+
}, 5);
118+
119+
// The globstar pattern is used here to make the call run longer than the settimeout.
120+
const action = fg.glob(['**'], { signal: ac.signal });
121+
122+
await assert.rejects(() => action, { message: 'This operation was aborted' });
123+
});
124+
125+
it('should abort processing static pattern with abort signal', async () => {
126+
const ac = new AbortController();
127+
128+
ac.abort();
129+
130+
const action = fg.glob(['./package.json'], { signal: ac.signal });
131+
132+
await assert.rejects(() => action, { message: 'The operation was aborted' });
133+
});
111134
});
112135

113136
describe('.async', () => {
@@ -175,6 +198,44 @@ describe('Package', () => {
175198
done();
176199
});
177200
});
201+
202+
it('should abort processing dynamic pattern with abort signal', (done) => {
203+
const ac = new AbortController();
204+
205+
setTimeout(() => {
206+
ac.abort();
207+
}, 5);
208+
209+
// The globstar pattern is used here to make the call run longer than the settimeout.
210+
const steam = fg.globStream(['**'], { signal: ac.signal });
211+
212+
steam.once('error', (error: ErrnoException) => {
213+
assert.strictEqual(error.message, 'This operation was aborted');
214+
done();
215+
});
216+
217+
steam.once('end', () => {
218+
assert.fail('The stream should be aborted');
219+
});
220+
});
221+
222+
it('should abort processing static pattern with abort signal', (done) => {
223+
const ac = new AbortController();
224+
225+
ac.abort();
226+
227+
// The globstar pattern is used here to make the call run longer than the settimeout.
228+
const steam = fg.globStream(['./package.json'], { signal: ac.signal });
229+
230+
steam.once('error', (error: ErrnoException) => {
231+
assert.strictEqual(error.message, 'The operation was aborted');
232+
done();
233+
});
234+
235+
steam.once('end', () => {
236+
assert.fail('The stream should be aborted');
237+
});
238+
});
178239
});
179240

180241
describe('.stream', () => {

src/providers/provider.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ describe('Providers → Provider', () => {
8585
assert.ok(!actual.stats);
8686
assert.ok(actual.throwErrorOnBrokenSymbolicLink === false);
8787
assert.strictEqual(typeof actual.transform, 'function');
88+
assert.strictEqual(actual.signal, undefined);
8889
});
8990

9091
it('should return options for reader with non-global base', () => {

src/providers/provider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export abstract class Provider<T> {
4848
stats: this.#settings.stats,
4949
throwErrorOnBrokenSymbolicLink: this.#settings.throwErrorOnBrokenSymbolicLink,
5050
transform: this.entryTransformer.getTransformer(),
51+
signal: this.#settings.signal,
5152
};
5253
}
5354

src/readers/stream.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class ReaderStream extends Reader<Readable> implements IReaderStream {
2424
public static(patterns: Pattern[], options: ReaderOptions): Readable {
2525
const filepaths = patterns.map((pattern) => this._getFullEntryPath(pattern));
2626

27-
const stream = new PassThrough({ objectMode: true });
27+
const stream = new PassThrough({ objectMode: true, signal: options.signal });
2828

2929
stream._write = (index: number, _enc, done) => {
3030
this.#getEntry(filepaths[index], patterns[index], options)

src/settings.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ describe('Settings', () => {
2828
assert.ok(settings.onlyFiles);
2929
assert.ok(settings.unique);
3030
assert.strictEqual(settings.cwd, process.cwd());
31+
assert.strictEqual(settings.signal, undefined);
3132
});
3233

3334
it('should return instance with custom values', () => {

src/settings.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ export interface Options {
138138
* @default true
139139
*/
140140
unique?: boolean;
141+
/**
142+
* A signal to abort searching for entries on the file system.
143+
* Works only with asynchronous methods for dynamic and static patterns.
144+
*
145+
* @default undefined
146+
*/
147+
signal?: AbortSignal;
141148
}
142149

143150
export default class Settings {
@@ -161,6 +168,7 @@ export default class Settings {
161168
public readonly suppressErrors: boolean;
162169
public readonly throwErrorOnBrokenSymbolicLink: boolean;
163170
public readonly unique: boolean;
171+
public readonly signal?: AbortSignal;
164172

165173
// eslint-disable-next-line complexity
166174
constructor(options: Options = {}) {
@@ -184,6 +192,7 @@ export default class Settings {
184192
this.suppressErrors = options.suppressErrors ?? false;
185193
this.throwErrorOnBrokenSymbolicLink = options.throwErrorOnBrokenSymbolicLink ?? false;
186194
this.unique = options.unique ?? true;
195+
this.signal = options.signal;
187196

188197
if (this.onlyDirectories) {
189198
this.onlyFiles = false;

0 commit comments

Comments
 (0)