简体   繁体   English

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

[英]How to promisify Node's child_process.exec and child_process.execFile functions with Bluebird?

I'm using the Bluebird promise library under Node.js, it's great!我在 Node.js 下使用 Bluebird promise 库,很棒! But I have a question:但我有一个问题:

If you take a look at the documentation of Node's child_process.exec and child_process.execFile you can see that both of these functions are returning a ChildProcess object.如果您查看 Node 的child_process.execchild_process.execFile的文档,您会发现这两个函数都返回一个 ChildProcess 对象。

So what's the recommended way to promisify such functions?那么承诺这些功能的推荐方法是什么?

Note that the following works (I get a Promise object):请注意,以下工作(我得到一个 Promise 对象):

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

But how can one get access to the original return value of the original Node.js functions?但是如何才能访问原始 Node.js 函数的原始返回值呢? (In these cases I would need to be able to access the originally returned ChildProcess objects.) (在这些情况下,我需要能够访问最初返回的 ChildProcess 对象。)

Any suggestion would be appreciated!任何建议将不胜感激!

EDIT:编辑:

Here is an example code which is using the return value of the child_process.exec function:这是使用 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);
});

But if I would use the promisified version of the exec function ( execAsync from above ) then the return value will be a promise, not a ChildProcess object.但是如果我使用 exec 函数的 promisified 版本(上面的 execAsync ),那么返回值将是一个承诺,而不是一个 ChildProcess 对象。 This is the real problem I am talking about.这是我正在谈论的真正问题。

I would recommend using standard JS promises built into the language over an additional library dependency like Bluebird.我建议使用语言中内置的标准 JS 承诺,而不是像 Bluebird 这样的附加库依赖项。

If you're using Node 10+, the Node.js docs recommend using util.promisify which returns a Promise<{ stdout, stderr }> object.如果您使用的是 Node 10+, Node.js 文档建议使用util.promisify ,它返回一个Promise<{ stdout, stderr }>对象。 See an example below:请参阅下面的示例:

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

Handle errors first from stderr .首先从stderr处理错误。

It sounds like you'd like to return two things from the call:听起来您想从通话中返回两件事:

  • the ChildProcess子进程
  • a promise that resolves when the ChildProcess completes在 ChildProcess 完成时解决的承诺

So "the recommended way to promisify such functions"?那么“承诺此类功能的推荐方法”? Don't .不要

You're outside the convention.你在公约之外。 Promise returning functions are expected to return a promise, and that's it. Promise 返回函数应该返回一个 Promise,仅此而已。 You could return an object with two members (the ChildProcess & the promise), but that'll just confuse people.您可以返回具有两个成员(ChildProcess 和承诺)的对象,但这只会使人们感到困惑。

I'd suggest calling the unpromisified function, and creating a promise based off the returned childProcess.我建议调用 unpromisified 函数,并根据返回的 childProcess 创建一个承诺。 (Maybe wrap that into a helper function) (也许把它包装成一个辅助函数)

This way, it's quite explicit for the next person who reads the code.这样,对于下一个阅读代码的人来说,这是非常明确的。

Something like:就像是:

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

Here's another way:这是另一种方式:

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

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

Use the function:使用函数:

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

Or with async/await:或者使用异步/等待:

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

Since Node v12 the built-in util.promisify allows access to the ChildProcess object in the returned Promise for built-in functions where it would have been returned by the un-promisified call.从 Node v12 开始,内置的util.promisify允许访问返回的PromiseChildProcess对象,用于内置函数,它本应由未承诺的调用返回。 From the docs :文档

The returned ChildProcess instance is attached to the Promise as a child property.返回的ChildProcess实例作为child属性附加到Promise

This correctly and simply satisfies the need to access ChildProcess in the original question and makes other answers out of date providing that Node v12+ can be used.这正确且简单地满足了在原始问题中访问ChildProcess的需要,并使其他答案过时, ChildProcess是可以使用 Node v12+。

Adapting the example (and concise style) provided by the questioner, access to the ChildProcess can be achieved like:改编提问者提供的示例(和简洁风格),可以像这样实现对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;

There's probably not a way to do nicely that covers all use cases.可能没有一种方法可以很好地涵盖所有用例。 But for limited cases, you can do something like this:但是对于有限的情况,您可以执行以下操作:

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

This accepts opts.stdout and opts.stderr arguments, so that stdio can be captured from the child process.这接受opts.stdoutopts.stderr参数,以便可以从子进程中捕获 stdio。

For example:例如:

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

Or simply:或者干脆:

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

Just want to mention that there's a nice tool that will solve your problem completely:只想提一下,有一个很好的工具可以完全解决您的问题:

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

This package makes it a lot easier to handle processes.这个包使得处理过程变得更加容易。

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

or combine these functions:或结合这些功能:

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

Here's mine.这是我的。 It doesn't deal with stdin or stdout, so if you need those then use one of the other answers on this page.它不处理标准输入或标准输出,因此如果您需要这些,请使用此页面上的其他答案之一。 :) :)

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

Here are my two cents.这是我的两分钱。 Uses spawn which streams the output and writes to stdout and stderr .使用 spawn 流输出并写入stdoutstderr The error and standard output is captured in buffers and are returned or rejected.错误和标准输出在缓冲区中捕获并返回或拒绝。

This is written I Typescript, feel free to remove typings if using JavaScript:这是我写的 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));
    });
  });

Just another example you might run into issues when running multiple commands when destructuring with the same const's you can rename them like this.再举一个例子,在使用相同的常量解构时运行多个命令时可能会遇到问题,您可以像这样重命名它们。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM