diff --git a/index.js b/index.js index cf63abef1..a1ad11464 100644 --- a/index.js +++ b/index.js @@ -258,34 +258,23 @@ const execa = (file, args, options) => { }, parsed.options.timeout); } - const resolvable = (() => { - let extracted; - const promise = new Promise(resolve => { - extracted = resolve; - }); - promise.resolve = extracted; - return promise; - })(); - // TODO: Use native "finally" syntax when targeting Node.js 10 - const processDone = pFinally(new Promise(resolve => { + const processDone = pFinally(new Promise((resolve, reject) => { spawned.on('exit', (code, signal) => { if (timedOut) { - resolvable.resolve([ - {code, signal}, '', '', '' - ]); + return reject(Object.assign(new Error('Timed out'), {code, signal})); } resolve({code, signal}); }); spawned.on('error', error => { - resolve({error}); + reject(error); }); if (spawned.stdin) { spawned.stdin.on('error', error => { - resolve({error}); + reject(error); }); } }), cleanup); @@ -305,22 +294,21 @@ const execa = (file, args, options) => { } const handlePromise = () => { - let processComplete = Promise.all([ + const processComplete = Promise.all([ processDone, getStream(spawned, 'stdout', {encoding, buffer, maxBuffer}), getStream(spawned, 'stderr', {encoding, buffer, maxBuffer}), getStream(spawned, 'all', {encoding, buffer, maxBuffer: maxBuffer * 2}) ]); - if (timeoutId) { - processComplete = Promise.race([ - processComplete, - resolvable - ]); - } - const finalize = async () => { - const results = await processComplete; + let results; + try { + results = await processComplete; + } catch (error) { + const {stream, code, signal} = error; + results = [{error, stream, code, signal}, '', '', '']; + } const [result, stdout, stderr, all] = results; result.stdout = handleOutput(parsed.options, stdout); diff --git a/test.js b/test.js index fb0cd8aec..ebe1a3796 100644 --- a/test.js +++ b/test.js @@ -65,15 +65,11 @@ test('stdout/stderr/all are undefined if ignored in sync mode', t => { t.is(all, undefined); }); -const WRONG_COMMAND_STDERR = process.platform === 'win32' ? - '\'wrong\' is not recognized as an internal or external command,\r\noperable program or batch file.' : - ''; - test('stdout/stderr/all on process errors', async t => { const {stdout, stderr, all} = await t.throwsAsync(execa('wrong command')); t.is(stdout, ''); - t.is(stderr, WRONG_COMMAND_STDERR); - t.is(all, WRONG_COMMAND_STDERR); + t.is(stderr, ''); + t.is(all, ''); }); test('stdout/stderr/all on process errors, in sync mode', t => { @@ -81,7 +77,9 @@ test('stdout/stderr/all on process errors, in sync mode', t => { execa.sync('wrong command'); }); t.is(stdout, ''); - t.is(stderr, WRONG_COMMAND_STDERR); + t.is(stderr, process.platform === 'win32' ? + '\'wrong\' is not recognized as an internal or external command,\r\noperable program or batch file.' : + ''); t.is(all, undefined); }); @@ -211,6 +209,12 @@ test('helpful error trying to provide an input stream in sync mode', t => { ); }); +test('child process errors rejects promise right away', async t => { + const child = execa('forever'); + child.emit('error', new Error('test')); + await t.throwsAsync(child, /test/); +}); + test('execa() returns a promise with kill() and pid', t => { const {kill, pid} = execa('noop', ['foo']); t.is(typeof kill, 'function');