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



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

如果查看Node的child_process.exec和child_proccess.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函数的promise版本(上面的execAsync(,那么返回值将是promise,而不是ChildProcess对象。这才是我所说的真正的问题。

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

如果使用Node 10+,Node.js文档建议使用返回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完成时解决的承诺

所以"承诺这些功能的推荐方式">不要

你在大会之外。Promise返回函数应该返回一个Promise,仅此而已。您可以返回一个有两个成员的对象(ChildProcess&the Promise(,但这只会让人感到困惑。

我建议调用未承诺的函数,并根据返回的childProcess创建一个承诺。(也许把它包装成一个辅助函数(

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

类似于:

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(),那么在最近的节点版本中,这里有一个更好的答案。

由于Node v12,内置的util.promisify允许访问返回的Promise中的ChildProcess对象,用于未承诺调用返回的内置函数。来自文档:

返回的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;

这里有另一种方法:

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

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

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

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

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

另一个例子是,当使用相同的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()

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

这是I 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));
    });
  });

这是我的。它不处理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);
            }
        });
    });
};

最新更新