Skip to content

Commit

Permalink
Support AbortController (#490)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
jopemachine and sindresorhus committed Feb 13, 2022
1 parent 93ab929 commit c6e791a
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 7 deletions.
30 changes: 30 additions & 0 deletions index.d.ts
Expand Up @@ -208,6 +208,34 @@ export interface CommonOptions<EncodingType> {
*/
readonly killSignal?: string | number;

/**
You can abort the spawned process using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
When `AbortController.abort()` is called, [`.isCanceled`](https://github.com/sindresorhus/execa#iscanceled) becomes `false`.
*Requires Node.js 16 or later.*
@example
```js
import {execa} from 'execa';
const abortController = new AbortController();
const subprocess = execa('node', [], {signal: abortController.signal});
setTimeout(() => {
abortController.abort();
}, 1000);
try {
await subprocess;
} catch (error) {
console.log(subprocess.killed); // true
console.log(error.isCanceled); // true
}
```
*/
readonly signal?: AbortSignal;

/**
If `true`, no quoting or escaping of arguments is done on Windows. Ignored on other platforms. This is set to `true` automatically when the `shell` option is `true`.
Expand Down Expand Up @@ -341,6 +369,8 @@ export interface ExecaReturnValue<StdoutErrorType = string>

/**
Whether the process was canceled.
You can cancel the spawned process using the [`signal`](https://github.com/sindresorhus/execa#signal-1) option.
*/
isCanceled: boolean;
}
Expand Down
2 changes: 1 addition & 1 deletion index.js
Expand Up @@ -127,7 +127,7 @@ export function execa(file, args, options) {
escapedCommand,
parsed,
timedOut,
isCanceled: context.isCanceled,
isCanceled: context.isCanceled || (parsed.options.signal ? parsed.options.signal.aborted : false),
killed: spawned.killed,
});

Expand Down
1 change: 1 addition & 0 deletions index.test-d.ts
Expand Up @@ -142,6 +142,7 @@ execa('unicorns', {timeout: 1000});
execa('unicorns', {maxBuffer: 1000});
execa('unicorns', {killSignal: 'SIGTERM'});
execa('unicorns', {killSignal: 9});
execa('unicorns', {signal: new AbortController().signal});
execa('unicorns', {windowsVerbatimArguments: true});
execa('unicorns', {windowsHide: false});
/* eslint-enable @typescript-eslint/no-floating-promises */
Expand Down
21 changes: 15 additions & 6 deletions readme.md
Expand Up @@ -83,10 +83,11 @@ try {
```js
import {execa} from 'execa';

const subprocess = execa('node');
const abortController = new AbortController();
const subprocess = execa('node', [], {signal: abortController.signal});

setTimeout(() => {
subprocess.cancel();
abortController.abort();
}, 1000);

try {
Expand Down Expand Up @@ -171,10 +172,6 @@ Milliseconds to wait for the child process to terminate before sending `SIGKILL`

Can be disabled with `false`.

#### cancel()

Similar to [`childProcess.kill()`](https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal). This is preferred when cancelling the child process execution as the error is more descriptive and [`childProcessResult.isCanceled`](#iscanceled) is set to `true`.

#### all

Type: `ReadableStream | undefined`
Expand Down Expand Up @@ -290,6 +287,8 @@ Type: `boolean`

Whether the process was canceled.

You can cancel the spawned process using the [`signal`](#signal-1) option.

#### killed

Type: `boolean`
Expand Down Expand Up @@ -546,6 +545,16 @@ Default: `SIGTERM`

Signal value to be used when the spawned process will be killed.

#### signal

Type: [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)

You can abort the spawned process using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).

When `AbortController.abort()` is called, [`.isCanceled`](#iscanceled) becomes `false`.

*Requires Node.js 16 or later.*

#### windowsVerbatimArguments

Type: `boolean`\
Expand Down
47 changes: 47 additions & 0 deletions test/kill.js
Expand Up @@ -262,3 +262,50 @@ test('calling cancel method on a process which has been killed does not make err
const {isCanceled} = await t.throwsAsync(subprocess);
t.false(isCanceled);
});

if (globalThis.AbortController !== undefined) {
test('calling abort throws an error with message "Command was canceled"', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', [], {signal: abortController.signal});
abortController.abort();
await t.throwsAsync(subprocess, {message: /Command was canceled/});
});

test('calling abort twice should show the same behaviour as calling it once', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', [], {signal: abortController.signal});
abortController.abort();
abortController.abort();
const {isCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(subprocess.killed);
});

test('calling abort on a successfully completed process does not make result.isCanceled true', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', [], {signal: abortController.signal});
const {isCanceled} = await subprocess;
abortController.abort();
t.false(isCanceled);
});

test('calling cancel after abort should show the same behaviour as only calling cancel', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', [], {signal: abortController.signal});
abortController.abort();
subprocess.cancel();
const {isCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(subprocess.killed);
});

test('calling abort after cancel should show the same behaviour as only calling cancel', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', [], {signal: abortController.signal});
subprocess.cancel();
abortController.abort();
const {isCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(subprocess.killed);
});
}

0 comments on commit c6e791a

Please sign in to comment.