Skip to content

Commit

Permalink
Fix async early exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jun 10, 2019
1 parent a7b57d9 commit 6f07979
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 1 deletion.
6 changes: 5 additions & 1 deletion index.js
Expand Up @@ -12,6 +12,7 @@ const mergeStream = require('merge-stream');
const pFinally = require('p-finally');
const onExit = require('signal-exit');
const stdio = require('./lib/stdio');
const mergePrototypes = require('./lib/merge');

const TEN_MEGABYTES = 1000 * 1000 * 10;

Expand Down Expand Up @@ -247,13 +248,16 @@ const execa = (file, args, options) => {
try {
spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options);
} catch (error) {
return Promise.reject(makeError({error, stdout: '', stderr: '', all: ''}, {
const promise = Promise.reject(makeError({error, stdout: '', stderr: '', all: ''}, {
joinedCommand,
parsed,
timedOut: false,
isCanceled: false,
killed: false
}));
// We make sure `child_process` properties are present even though no child process was created.
// This is to ensure the return value always has the same shape.
return mergePrototypes(promise, new childProcess.ChildProcess());
}

const kill = spawned.kill.bind(spawned);
Expand Down
24 changes: 24 additions & 0 deletions lib/merge.js
@@ -0,0 +1,24 @@
'use strict';

// Merge two objects, including their prototypes
function mergePrototypes(to, from) {
const prototypes = [...getPrototypes(to), ...getPrototypes(from)];
const newPrototype = prototypes.reduce(reducePrototype, {});
return Object.assign(Object.setPrototypeOf(to, newPrototype), to, from);
}

function getPrototypes(object, prototypes = []) {
const prototype = Object.getPrototypeOf(object);
if (prototype !== null) {
return getPrototypes(prototype, [...prototypes, prototype]);
}

return prototypes;
}

function reducePrototype(prototype, constructor) {
return Object.defineProperties(prototype, Object.getOwnPropertyDescriptors(constructor));
}

module.exports = mergePrototypes;

9 changes: 9 additions & 0 deletions test.js
Expand Up @@ -288,6 +288,15 @@ test('execa() returns a promise with kill() and pid', t => {
t.is(typeof pid, 'number');
});

test('child_process.spawn() propagated errors have correct shape', t => {
const cp = execa('noop', {uid: -1});
t.notThrows(() => {
cp.catch(() => {});
cp.unref();
cp.on('error', () => {});
});
});

test('child_process.spawn() errors are propagated', async t => {
const {exitCodeName} = await t.throwsAsync(execa('noop', {uid: -1}));
t.is(exitCodeName, process.platform === 'win32' ? 'ENOTSUP' : 'EINVAL');
Expand Down

0 comments on commit 6f07979

Please sign in to comment.