如何使用 Bluebird 承诺 Node 的 child_process.exec 和 child_process.execFile 函数?

Posted

技术标签:

【中文标题】如何使用 Bluebird 承诺 Node 的 child_process.exec 和 child_process.execFile 函数?【英文标题】:How to promisify Node's child_process.exec and child_process.execFile functions with Bluebird? 【发布时间】:2015-08-26 03:03:57 【问题描述】:

我正在使用 Node.js 下的 Bluebird Promise 库,非常棒!但我有一个问题:

如果您查看 Node 的 child_process.exec 和 child_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 对象。这才是我要说的真正问题。

【问题讨论】:

你需要promise和ChildProcess实例吗?您希望如何使用所需功能的代码示例会很有帮助。 @Bergi 是的,完全正确!我将需要承诺和子进程对象。这更像是一个理论问题,因为我解决了我的问题。但这是我想做的:我想用 child_process.execFile 执行一个程序,然后我想将数据输入(管道)到它的标准输入并读取它的标准输出。我需要一个承诺,因为承诺链。无论如何,我通过承诺 child_process.exec 而不是 execFile 来解决它,并通过这样的 shell 运行程序:prg <input >output。但现在我必须对所有内容进行 shell 转义(在 Windows 和 *nix 上)... 如果你只是想访问stdout/err,你不需要返回的对象。因为 stdout/err 是回调函数的参数。 查看 npmjs.com/package/child-process-promise 及其关联代码 (github.com/patrick-steele-idem/child-process-promise)。 或者你可能喜欢github.com/jcoreio/promisify-child-process的API,它可以让你简单地const stdout, stderr = await exec('echo test') 【参考方案1】:

我建议使用语言中内置的标准 JS 承诺,而不是像 Bluebird 这样的附加库依赖项。

如果您使用的是 Node 10+,the Node.js docs 建议使用返回 Promise< stdout, stderr > 对象的 util.promisify。请参阅下面的示例:

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 的错误。

【讨论】:

这太棒了! 这应该是公认的答案,它也适用于许多其他基于回调的函数 这个答案忽略了问题的核心前提:“访问最初返回的 ChildProcess 对象”。【参考方案2】:

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

子进程 当 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);
);

如果您只是想特别承诺child_process.exec()child_process.execFile(),在最近的节点版本中有更好的答案here。

【讨论】:

非常感谢,让我开始走上正轨!我需要稍微调整一下,以便正确处理退出代码:child.addListener('exit', (code, signal) => if (code === 0) resolve(); else reject(); ); child.stderr.on 回调中,记录stderr 而不是stdout 会更清晰。 感谢@GreenRaccoon23。错字已修复。 这个问题/答案+1;受这个答案的启发,我找到了parallel-mochas.js,这是一个使用 ES6 承诺而不是蓝鸟依赖的类似示例。 这里使用.catch(err) instead of using the reject handler in .then()`不是更好吗?【参考方案3】:

从 Node v12 开始,内置 util.promisify 允许访问返回的 Promise 中的 ChildProcess 对象,用于内置函数,如果未承诺的调用会返回该对象。来自docs:

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

这正确且简单地满足了在原始问题中访问 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;

【讨论】:

谢谢。我找到了那些文档,但不明白。你的例子很有帮助。 不错!貌似在[v12.6.0](github.com/nodejs/node/pull/28508)中引入【参考方案4】:

这是另一种方式:

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

【讨论】:

如果你想流式传输标准输出或标准错误,这不适合,例如当它们非常大时。 也可以使用util.promisify,然后访问.stdout @Lucas 您应该将其发布为答案。 const execAsync = require('util').promisify(require('child_process').exec);【参考方案5】:

可能没有一种方法可以很好地涵盖所有用例。但对于有限的情况,您可以这样做:

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

【讨论】:

【参考方案6】:

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

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 */);

【讨论】:

根据这个建议,我开始使用 core-worker。我发现它在提供输出方面非常不透明,并且发现当命令正确完成时它会抛出非零退出代码。我不会使用那个库。 如果您在使用 lib 时遇到问题,请您好心并提出一个/多个问题吗?我们在涉及外部流程的任何地方都在使用它,它是我们在数十个生产项目中使用的功能测试套件的核心库。【参考方案7】:

这是我的。它不处理标准输入或标准输出,因此如果您需要这些,请使用此页面上的其他答案之一。 :)

// 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”问题的第一个选项,但我可以确认这确实有效。【参考方案8】:

另外一个例子,在使用相同的 const 进行解构时运行多个命令时可能会遇到问题,您可以像这样重命名它们。

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

async function runCommands() 
    try 
        const  stdout, stderr  = await exec('ls');
        console.log('stdout:', stdout);
        console.log('stderr:', stderr);

        const  stdout: stdoutTwo, stderr: stderrTwo  = await exec('ls');
        console.log('stdoutTwo:', stdoutTwo);
        console.log('stderrTwo:', stderrTwo);

        const  stdout: stdoutThree, stderr: stderrThree  = await exec('ls');
        console.log('stdoutThree:', stdoutThree);
        console.log('stderrThree:', stderrThree);

     catch (e) 
        console.error(e); // should contain code (exit code) and signal (that caused the termination).
    

runCommands()

【讨论】:

【参考方案9】:

这是我的两分钱。使用 spawn 流式传输输出并写入 stdoutstderr。错误和标准输出被捕获在缓冲区中并被返回或拒绝。

这是用 Typescript 编写的,如果使用 javascript,请随意删除打字:

import  spawn, SpawnOptionsWithoutStdio  from 'child_process'

const spawnAsync = async (
  command: string,
  options?: SpawnOptionsWithoutStdio
) =>
  new Promise<Buffer>((resolve, reject) => 
    const [spawnCommand, ...args] = command.split(/\s+/);
    const spawnProcess = spawn(spawnCommand, args, options);
    const chunks: Buffer[] = [];
    const errorChunks: Buffer[] = [];
    spawnProcess.stdout.on("data", (data) => 
      process.stdout.write(data.toString());
      chunks.push(data);
    );
    spawnProcess.stderr.on("data", (data) => 
      process.stderr.write(data.toString());
      errorChunks.push(data);
    );
    spawnProcess.on("error", (error) => 
      reject(error);
    );
    spawnProcess.on("close", (code) => 
      if (code === 1) 
        reject(Buffer.concat(errorChunks).toString());
        return;
      
      resolve(Buffer.concat(chunks));
    );
  );

【讨论】:

以上是关于如何使用 Bluebird 承诺 Node 的 child_process.exec 和 child_process.execFile 函数?的主要内容,如果未能解决你的问题,请参考以下文章

NPM CI 和 Bluebird 承诺警告

如何使用 Bluebird 承诺 NodeJS Express

如何使用 Bluebird 在构造函数构建的“类”上承诺导出的函数

与 fs 和 bluebird 的承诺

如何使用 PromiseKit 重构快速回调以承诺

为啥在使用promise时使用Q,bluebird框架? [复制]