簡體   English   中英

如何使用 Bluebird 保證 Node 的 child_process.exec 和 child_process.execFile 函數?

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

我在 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 函數的 promisified 版本(上面的 execAsync ),那么返回值將是一個承諾,而不是一個 ChildProcess 對象。 這是我正在談論的真正問題。

我建議使用語言中內置的標准 JS 承諾,而不是像 Bluebird 這樣的附加庫依賴項。

如果您使用的是 Node 10+, Node.js 文檔建議使用util.promisify ,它返回一個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處理錯誤。

聽起來您想從通話中返回兩件事:

  • 子進程
  • 在 ChildProcess 完成時解決的承諾

那么“承諾此類功能的推薦方法”? 不要

你在公約之外。 Promise 返回函數應該返回一個 Promise,僅此而已。 您可以返回具有兩個成員(ChildProcess 和承諾)的對象,但這只會使人們感到困惑。

我建議調用 unpromisified 函數,並根據返回的 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);
});

這是另一種方式:

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

從 Node v12 開始,內置的util.promisify允許訪問返回的PromiseChildProcess對象,用於內置函數,它本應由未承諾的調用返回。 文檔

返回的ChildProcess實例作為child屬性附加到Promise

這正確且簡單地滿足了在原始問題中訪問ChildProcess的需要,並使其他答案過時, 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;

可能沒有一種方法可以很好地涵蓋所有用例。 但是對於有限的情況,您可以執行以下操作:

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

這是我的。 它不處理標准輸入或標准輸出,因此如果您需要這些,請使用此頁面上的其他答案之一。 :)

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

這是我的兩分錢。 使用 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));
    });
  });

再舉一個例子,在使用相同的常量解構時運行多個命令時可能會遇到問題,您可以像這樣重命名它們。

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