Skip to content

Commit 6f07979

Browse files
committed
Fix async early exceptions
1 parent a7b57d9 commit 6f07979

File tree

3 files changed

+38
-1
lines changed

3 files changed

+38
-1
lines changed

index.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const mergeStream = require('merge-stream');
1212
const pFinally = require('p-finally');
1313
const onExit = require('signal-exit');
1414
const stdio = require('./lib/stdio');
15+
const mergePrototypes = require('./lib/merge');
1516

1617
const TEN_MEGABYTES = 1000 * 1000 * 10;
1718

@@ -247,13 +248,16 @@ const execa = (file, args, options) => {
247248
try {
248249
spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options);
249250
} catch (error) {
250-
return Promise.reject(makeError({error, stdout: '', stderr: '', all: ''}, {
251+
const promise = Promise.reject(makeError({error, stdout: '', stderr: '', all: ''}, {
251252
joinedCommand,
252253
parsed,
253254
timedOut: false,
254255
isCanceled: false,
255256
killed: false
256257
}));
258+
// We make sure `child_process` properties are present even though no child process was created.
259+
// This is to ensure the return value always has the same shape.
260+
return mergePrototypes(promise, new childProcess.ChildProcess());
257261
}
258262

259263
const kill = spawned.kill.bind(spawned);

lib/merge.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
// Merge two objects, including their prototypes
4+
function mergePrototypes(to, from) {
5+
const prototypes = [...getPrototypes(to), ...getPrototypes(from)];
6+
const newPrototype = prototypes.reduce(reducePrototype, {});
7+
return Object.assign(Object.setPrototypeOf(to, newPrototype), to, from);
8+
}
9+
10+
function getPrototypes(object, prototypes = []) {
11+
const prototype = Object.getPrototypeOf(object);
12+
if (prototype !== null) {
13+
return getPrototypes(prototype, [...prototypes, prototype]);
14+
}
15+
16+
return prototypes;
17+
}
18+
19+
function reducePrototype(prototype, constructor) {
20+
return Object.defineProperties(prototype, Object.getOwnPropertyDescriptors(constructor));
21+
}
22+
23+
module.exports = mergePrototypes;
24+

test.js

+9
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,15 @@ test('execa() returns a promise with kill() and pid', t => {
288288
t.is(typeof pid, 'number');
289289
});
290290

291+
test('child_process.spawn() propagated errors have correct shape', t => {
292+
const cp = execa('noop', {uid: -1});
293+
t.notThrows(() => {
294+
cp.catch(() => {});
295+
cp.unref();
296+
cp.on('error', () => {});
297+
});
298+
});
299+
291300
test('child_process.spawn() errors are propagated', async t => {
292301
const {exitCodeName} = await t.throwsAsync(execa('noop', {uid: -1}));
293302
t.is(exitCodeName, process.platform === 'win32' ? 'ENOTSUP' : 'EINVAL');

0 commit comments

Comments
 (0)