Skip to content

Commit

Permalink
Remove .cancel() method (#711)
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jan 22, 2024
1 parent 472c065 commit 16e5fd6
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 112 deletions.
9 changes: 1 addition & 8 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,9 +670,7 @@ type ExecaCommonReturnValue<IsSync extends boolean = boolean, OptionsType extend
cwd: string;

/**
Whether the process was canceled.
You can cancel the spawned process using the [`signal`](https://github.com/sindresorhus/execa#signal-1) option.
Whether the process was canceled using the [`signal`](https://github.com/sindresorhus/execa#signal-1) option.
*/
isCanceled: boolean;

Expand Down Expand Up @@ -783,11 +781,6 @@ export type ExecaChildPromise<OptionsType extends Options = Options> = {
*/
kill(signal?: string, options?: KillOptions): void;

/**
Similar to [`childProcess.kill()`](https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal). This used to be preferred when cancelling the child process execution as the error is more descriptive and [`childProcessResult.isCanceled`](#iscanceled) is set to `true`. But now this is deprecated and you should either use `.kill()` or the `signal` option when creating the child process.
*/
cancel(): void;

/**
[Pipe](https://nodejs.org/api/stream.html#readablepipedestination-options) the child process's `stdout` to `target`, which can be:
- Another `execa()` return value
Expand Down
13 changes: 6 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {makeError} from './lib/error.js';
import {handleInputAsync, pipeOutputAsync} from './lib/stdio/async.js';
import {handleInputSync, pipeOutputSync} from './lib/stdio/sync.js';
import {normalizeStdioNode} from './lib/stdio/normalize.js';
import {spawnedKill, spawnedCancel, validateTimeout} from './lib/kill.js';
import {spawnedKill, validateTimeout} from './lib/kill.js';
import {addPipeMethods} from './lib/pipe.js';
import {getSpawnedResult, makeAllStream} from './lib/stream.js';
import {mergePromise} from './lib/promise.js';
Expand Down Expand Up @@ -142,20 +142,19 @@ export function execa(rawFile, rawArgs, rawOptions) {

pipeOutputAsync(spawned, stdioStreamsGroups);

const context = {isCanceled: false, timedOut: false};

spawned.kill = spawnedKill.bind(null, spawned.kill.bind(spawned));
spawned.cancel = spawnedCancel.bind(null, spawned, context);
spawned.all = makeAllStream(spawned, options);

addPipeMethods(spawned);

const promise = handlePromise({spawned, options, context, stdioStreamsGroups, command, escapedCommand});
const promise = handlePromise({spawned, options, stdioStreamsGroups, command, escapedCommand});
mergePromise(spawned, promise);
return spawned;
}

const handlePromise = async ({spawned, options, context, stdioStreamsGroups, command, escapedCommand}) => {
const handlePromise = async ({spawned, options, stdioStreamsGroups, command, escapedCommand}) => {
const context = {timedOut: false};

const [
[exitCode, signal, error],
stdioResults,
Expand All @@ -165,7 +164,7 @@ const handlePromise = async ({spawned, options, context, stdioStreamsGroups, com
const all = handleOutput(options, allResult);

if (error || exitCode !== 0 || signal !== null) {
const isCanceled = context.isCanceled || Boolean(options.signal?.aborted);
const isCanceled = options.signal?.aborted === true;
const returnedError = makeError({
error,
exitCode,
Expand Down
1 change: 0 additions & 1 deletion index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ const syncGenerator = function * (lines: Iterable<string>) {

try {
const execaPromise = execa('unicorns', {all: true});
execaPromise.cancel();

const execaBufferPromise = execa('unicorns', {encoding: 'buffer', all: true});
const writeStream = createWriteStream('output.txt');
Expand Down
9 changes: 0 additions & 9 deletions lib/kill.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,6 @@ const getForceKillAfterTimeout = (signal, forceKillAfterTimeout, killResult) =>
return forceKillAfterTimeout;
};

// `childProcess.cancel()`
export const spawnedCancel = (spawned, context) => {
const killResult = spawned.kill();

if (killResult) {
context.isCanceled = true;
}
};

const killAfterTimeout = async ({spawned, timeout, killSignal, context, controller}) => {
await pSetTimeout(timeout, undefined, {ref: false, signal: controller.signal});
spawned.kill(killSignal);
Expand Down
4 changes: 1 addition & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,9 +461,7 @@ Whether the process timed out.

Type: `boolean`

Whether the process was canceled.

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

#### isTerminated

Expand Down
119 changes: 35 additions & 84 deletions test/kill.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,122 +182,73 @@ test('removes exit handler on exit', async t => {
t.false(exitListeners.includes(listener));
});

test('cancel method kills the subprocess', async t => {
const subprocess = execa('node');
subprocess.cancel();
t.true(subprocess.killed);
const {isTerminated} = await t.throwsAsync(subprocess);
t.true(isTerminated);
});

test('result.isCanceled is false when spawned.cancel() isn\'t called (success)', async t => {
test('result.isCanceled is false when abort isn\'t called (success)', async t => {
const {isCanceled} = await execa('noop.js');
t.false(isCanceled);
});

test('result.isCanceled is false when spawned.cancel() isn\'t called (failure)', async t => {
test('result.isCanceled is false when abort isn\'t called (failure)', async t => {
const {isCanceled} = await t.throwsAsync(execa('fail.js'));
t.false(isCanceled);
});

test('result.isCanceled is false when spawned.cancel() isn\'t called in sync mode (success)', t => {
test('result.isCanceled is false when abort isn\'t called in sync mode (success)', t => {
const {isCanceled} = execaSync('noop.js');
t.false(isCanceled);
});

test('result.isCanceled is false when spawned.cancel() isn\'t called in sync mode (failure)', t => {
test('result.isCanceled is false when abort isn\'t called in sync mode (failure)', t => {
const {isCanceled} = t.throws(() => {
execaSync('fail.js');
});
t.false(isCanceled);
});

test('calling cancel method throws an error with message "Command was canceled"', async t => {
const subprocess = execa('noop.js');
subprocess.cancel();
await t.throwsAsync(subprocess, {message: /Command was canceled/});
test('calling abort is not considered a signal termination', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', {signal: abortController.signal});
abortController.abort();
const {isTerminated, signal} = await t.throwsAsync(subprocess);
t.false(isTerminated);
t.is(signal, undefined);
});

test('error.isCanceled is true when cancel method is used', async t => {
const subprocess = execa('noop.js');
subprocess.cancel();
test('error.isCanceled is true when abort is used', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', {signal: abortController.signal});
abortController.abort();
const {isCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
});

test('error.isCanceled is false when kill method is used', async t => {
const subprocess = execa('noop.js');
const abortController = new AbortController();
const subprocess = execa('noop.js', {signal: abortController.signal});
subprocess.kill();
const {isCanceled} = await t.throwsAsync(subprocess);
t.false(isCanceled);
});

test('calling cancel method twice should show the same behaviour as calling it once', async t => {
const subprocess = execa('noop.js');
subprocess.cancel();
subprocess.cancel();
const {isCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(subprocess.killed);
});

test('calling cancel method on a successfully completed process does not make result.isCanceled true', async t => {
const subprocess = execa('noop.js');
const {isCanceled} = await subprocess;
subprocess.cancel();
t.false(isCanceled);
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 cancel method on a process which has been killed does not make error.isCanceled true', async t => {
const subprocess = execa('noop.js');
subprocess.kill();
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.false(isCanceled);
t.true(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);
});
}
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 result = await subprocess;
abortController.abort();
t.false(result.isCanceled);
});

0 comments on commit 16e5fd6

Please sign in to comment.