如何使用Bluebird来实现Node的child_process.exec和child_process.execFile函数的功能?


80

我正在Node.js下使用Bluebird Promise库,太好了!但是我有一个问题:

如果查看一下Node的child_process.execchild_process.execFile的文档,您会发现这两个函数都返回一个ChildProcess对象。

那么推荐这种功能的推荐方法是什么?

请注意以下工作(我得到一个Promise对象):

var Promise = require('bluebird');
var execAsync = Promise.promisify(require('child_process').exec);
var execFileAsync = Promise.promisify(require('child_process').execFile);

但是如何获得原始Node.js函数的原始返回值?(在这些情况下,我将需要能够访问最初返回的ChildProcess对象。)

任何建议,将不胜感激!

编辑:

这是使用child_process.exec函数的返回值的示例代码:

var exec = require('child_process').exec;
var child = exec('node ./commands/server.js');
child.stdout.on('data', function(data) {
    console.log('stdout: ' + data);
});
child.stderr.on('data', function(data) {
    console.log('stderr: ' + data);
});
child.on('close', function(code) {
    console.log('closing code: ' + code);
});

但是,如果我使用exec函数的承诺版本(上面的execAsync),则返回值将是一个承诺,而不是ChildProcess对象。这是我正在谈论的真正问题。


您是否需要诺言和ChildProcess实例?有关如何使用所需功能的代码示例将很有帮助。
Bergi 2015年

@Bergi是的,完全是!我本来需要promise和子流程对象。实际上,这更像一个理论问题,因为我解决了我的问题。但是,这是我想做的事情:我想使用child_process.execFile执行一个程序,然后想将数据送入(管道)到其stdin中并读取其stdout。由于承诺链,我需要一个承诺。总之,我的工作围绕它通过promisifying child_process.exec代替的execfile,并且通过这样的shell中运行该程序:prg <input >output。但是,现在我必须脱壳所有内容(包括Windows和* nix)……
Zoltan 2015年

如果只想访问stdout / err,则不需要返回的对象。因为stdout / err是回调函数的参数。
KFL


或者,你可能会喜欢的API github.com/jcoreio/promisify-child-process,它允许您简单const {stdout, stderr} = await exec('echo test')
安迪

Answers:


72

听起来您想从通话中返回两件事:

  • 子进程
  • 当ChildProcess完成时解决的承诺

那么,“推荐这种功能的推荐方法”呢?不要

您不符合常规。承诺返回函数将返回承诺,仅此而已。您可以返回一个具有两个成员的对象(ChildProcess和Promise),但这只会使人们感到困惑。

我建议调用unpromisified函数,并基于返回的childProcess创建一个Promise。(也许将其包装到一个辅助函数中)

这样,对于下一个阅读该代码的人来说是非常明确的。

就像是:

var Promise = require('bluebird');
var exec = require('child_process').execFile;

function promiseFromChildProcess(child) {
    return new Promise(function (resolve, reject) {
        child.addListener("error", reject);
        child.addListener("exit", resolve);
    });
}

var child = exec('ls');

promiseFromChildProcess(child).then(function (result) {
    console.log('promise complete: ' + result);
}, function (err) {
    console.log('promise rejected: ' + err);
});

child.stdout.on('data', function (data) {
    console.log('stdout: ' + data);
});
child.stderr.on('data', function (data) {
    console.log('stderr: ' + data);
});
child.on('close', function (code) {
    console.log('closing code: ' + code);
});

3
非常感谢您,这让我踏上了正确的路!我需要对其进行一些微调,以便正确处理退出代码:child.addListener('exit', (code, signal) => { if (code === 0) { resolve(); } else { reject(); } });
戴恩

2
child.stderr.on回调,记录stderr,而不是stdout会更清晰。
GreenRaccoon23 '16

谢谢@ GreenRaccoon23。错字固定。
伊万·汉密尔顿

为此问题/答案+1;受此答案的启发,我发现parallel-mochas.js这是使用ES6 Promise而不是bluebird依赖的类似示例。
mhulse

在这里使用.catch(err) instead of using the reject handler in .then()`会更好吗?
Miladinho

89

我建议使用语言内置的标准JS Promise,而不是使用诸如Bluebird之类的其他库依赖项。

如果您使用的是Node 10+,则Node.js文档建议使用util.promisifywhich返回一个Promise<{ stdout, stderr }>对象。请参阅以下示例:

const util = require('util');
const exec = util.promisify(require('child_process').exec);

async function lsExample() {
  try {
    const { stdout, stderr } = await exec('ls');
    console.log('stdout:', stdout);
    console.log('stderr:', stderr);
  } catch (e) {
    console.error(e); // should contain code (exit code) and signal (that caused the termination).
  }
}
lsExample()

首先从处理错误stderr


3
这真太了不起了!
electrovir

5
这应该是公认的答案,它也适用于其他许多基于回调的函数
Ben Winding

19

这是另一种方式:

function execPromise(command) {
    return new Promise(function(resolve, reject) {
        exec(command, (error, stdout, stderr) => {
            if (error) {
                reject(error);
                return;
            }

            resolve(stdout.trim());
        });
    });
}

使用功能:

execPromise(command).then(function(result) {
    console.log(result);
}).catch(function(e) {
    console.error(e.message);
});

或使用async / await:

try {
    var result = await execPromise(command);
} catch (e) {
    console.error(e.message);
}

1
如果要流式输出stdout或stderr,例如当它们非常大时,这是不合适的。
Jeroen

2
您还可以使用util.promisify,然后访问.stdout
卢卡斯

1
@Lucas您应该将其发布为答案。const execAsync = require('util').promisify(require('child_process').exec);
MrHIDEn

9

从Node v12开始,内置函数util.promisify允许访问ChildProcess返回Promise的内置函数中的对象,这些对象本应由未承诺的调用返回。从文档

返回的ChildProcess实例Promise作为child属性附加到。

如果可以使用Node v12 +,这可以正确,简单地满足访问ChildProcess原始问题的需要,并且可以使其他答案过时

改编提问者提供的示例(简洁的样式),ChildProcess可以像下面这样实现对的访问:

const util = require('util');
const exec = util.promisify(require('child_process').exec);
const promise = exec('node ./commands/server.js');
const child = promise.child; 

child.stdout.on('data', function(data) {
    console.log('stdout: ' + data);
});
child.stderr.on('data', function(data) {
    console.log('stderr: ' + data);
});
child.on('close', function(code) {
    console.log('closing code: ' + code);
});

// i.e. can then await for promisified exec call to complete
const { stdout, stderr } = await promise;

谢谢。我找到了那些文档,但听不懂。您的示例非常有帮助。
Fletcher Moore

7

可能没有一种很好的方法可以涵盖所有用例。但是在少数情况下,您可以执行以下操作:

/**
 * Promisified child_process.exec
 *
 * @param cmd
 * @param opts See child_process.exec node docs
 * @param {stream.Writable} opts.stdout If defined, child process stdout will be piped to it.
 * @param {stream.Writable} opts.stderr If defined, child process stderr will be piped to it.
 *
 * @returns {Promise<{ stdout: string, stderr: stderr }>}
 */
function execp(cmd, opts) {
    opts || (opts = {});
    return new Promise((resolve, reject) => {
        const child = exec(cmd, opts,
            (err, stdout, stderr) => err ? reject(err) : resolve({
                stdout: stdout,
                stderr: stderr
            }));

        if (opts.stdout) {
            child.stdout.pipe(opts.stdout);
        }
        if (opts.stderr) {
            child.stderr.pipe(opts.stderr);
        }
    });
}

这接受opts.stdout并接受opts.stderr参数,以便可以从子进程中捕获stdio。

例如:

execp('ls ./', {
    stdout: new stream.Writable({
        write: (chunk, enc, next) => {
            console.log(chunk.toString(enc));
            next();
        }
    }),
    stderr: new stream.Writable({
        write: (chunk, enc, next) => {
            console.error(chunk.toString(enc));
            next();
        }
    })
}).then(() => console.log('done!'));

或者简单地:

execp('ls ./', {
    stdout: process.stdout,
    stderr: process.stderr
}).then(() => console.log('done!'));

5

只想提一下,有一个很好的工具可以完全解决您的问题:

https://www.npmjs.com/package/core-worker

该软件包使处理过程变得容易得多。

import { process } from "CoreWorker";
import fs from "fs";

const result = await process("node Server.js", "Server is ready.").ready(1000);
const result = await process("cp path/to/file /newLocation/newFile").death();

或结合以下功能:

import { process } from "core-worker";

const simpleChat = process("node chat.js", "Chat ready");

setTimeout(() => simpleChat.kill(), 360000); // wait an hour and close the chat

simpleChat.ready(500)
    .then(console.log.bind(console, "You are now able to send messages."))
    .then(::simpleChat.death)
    .then(console.log.bind(console, "Chat closed"))
    .catch(() => /* handle err */);

9
基于此建议,我开始使用核心工作人员。我发现它在提供输出方面非常不透明,并且发现当命令正确完成时,它将抛出非零的退出代码。我不会使用该库。
pbanka

如果您在使用lib时遇到麻烦,您可以这么善良并提出一个问题吗?我们在涉及外部流程的任何地方都使用它,它是我们的功能测试套件的核心库,用于许多生产项目。
Tobias

0

这是我的。它不处理stdin或stdout,因此,如果需要这些内容,请使用此页面上的其他答案之一。:)

// promisify `child_process`
// This is a very nice trick :-)
this.promiseFromChildProcess = function (child) {
    return new Promise((resolve, reject) => {
        child.addListener('error', (code, signal) => {
            console.log('ChildProcess error', code, signal);
            reject(code);
        });
        child.addListener('exit', (code, signal) => {
            if (code === 0) {
                resolve(code);
            } else {
                console.log('ChildProcess error', code, signal);
                reject(code);
            }
        });
    });
};

Welp,也许这不是您想尝试解决“ child_process”问题的第一个选项,但是我可以确认这确实可行。
诗人
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.