如何使用 Bluebird 保证 Node 的 child_process.exec 和 child_process.execFile 函数?

IT技术 javascript node.js promise bluebird
2021-02-18 23:56:42

我在 Node.js 下使用 Bluebird promise 库,很棒!但我有一个问题:

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

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

请注意,以下工作(我得到一个 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 函数的 promisified 版本(上面的 execAsync ),那么返回值将是一个Promise,而不是一个 ChildProcess 对象。这是我所说的真正问题。

6个回答

我建议使用语言中内置的标准 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

这真太了不起了!
2021-05-07 23:56:42
这应该是公认的答案,它也适用于许多其他基于回调的函数
2021-05-15 23:56:42

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

  • 子进程
  • 在 ChildProcess 完成时解决的Promise

那么“Promise此类功能的推荐方法”?不要

你在公约之外。Promise 返回函数应该返回一个 Promise,仅此而已。您可以返回具有两个成员(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);
});
谢谢@GreenRaccoon23。错别字已修复。
2021-04-15 23:56:42
+1 此问题/答案;受到这个答案的启发,我发现parallel-mochas.js这是一个使用 ES6 Promise而不是蓝鸟依赖的类似示例。
2021-04-16 23:56:42
在这里使用.catch(err) instead of using the reject handler in .then()`不是更好吗?
2021-04-19 23:56:42
非常感谢这一点,让我开始走上正轨!我需要稍微调整一下,以便正确处理退出代码:child.addListener('exit', (code, signal) => { if (code === 0) { resolve(); } else { reject(); } });
2021-05-11 23:56:42
child.stderr.on回调中,记录stderr而不是记录stdout会更清楚。
2021-05-11 23:56:42

这是另一种方式:

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);
});

或者使用异步/等待:

try {
    var result = await execPromise(command);
} catch (e) {
    console.error(e.message);
}
@Lucas您应该将其发布为答案。 const execAsync = require('util').promisify(require('child_process').exec);
2021-04-20 23:56:42
您也可以使用util.promisify然后访问.stdout.
2021-04-22 23:56:42
如果您想流式传输 stdout 或 stderr,这不合适,例如当它们非常大时。
2021-04-24 23:56:42

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

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

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

改编提问者提供的示例(和简洁风格),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;
谢谢你。我找到了那些文档,但不明白。你的例子很有帮助。
2021-05-02 23:56:42

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

/**
 * 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.stdoutopts.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!'));