Jak promisify proces potomny węzła.exec i proces potomny.funkcje execFile z Bluebird?

Używam Biblioteki Bluebird promise pod Node.js, jest super! Ale mam pytanie:

Jeśli przyjrzysz się dokumentacji węzła child_process.exec i child_process.plik execFile widać, że obie te funkcje zwracają obiekt potomny.

Więc jaki jest zalecany sposób na promisify takich funkcji?

Zauważ, że następujące prace (otrzymuję obiekt obietnicy):

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

Ale jak można dostać dostęp do oryginalnej wartości zwracanej przez oryginalny węzeł.funkcje js? (W takich przypadkach musiałbym mieć dostęp do oryginalnie zwracanych obiektów potomnych.)

Każda sugestia będzie mile widziana!

EDIT:

Oto przykładowy kod, który używa zwracanej wartości child_process.funkcja 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);
});

Ale jeśli użyłbym promisified version of the Exec function (execAsync from above) to zwracana wartość będzie obietnicą, a nie Obiekt ChildProcess. To jest prawdziwy problem, o którym mówię.

Author: peterh - Reinstate Monica, 2015-06-10

7 answers

Wygląda na to, że chciałbyś zwrócić dwie rzeczy z połączenia:

  • The ChildProcess
  • obietnica, która rozwiązuje się, gdy proces dziecięcy się zakończy

Więc "zalecany sposób promisify takich funkcji"? Nie .

Jesteś poza konwentem. Oczekuje się, że funkcje zwracające obietnicę zwrócą obietnicę i to wszystko. Możesz zwrócić obiekt z dwoma członkami (proces potomny i obietnica), ale to tylko myli osób.

Sugerowałbym wywołanie funkcji niepromyfikowanej i stworzenie obietnicy opartej na zwracanym procesie potomnym. (Może zawiń to w funkcję pomocniczą)

W ten sposób, jest to dość wyraźne dla następnej osoby, która czyta kod.

Coś w stylu:

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);
});
 71
Author: Ivan Hamilton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-01 07:01:33

Zalecałbym użycie standardowego js promises wbudowanego w Język poprzez dodatkową zależność biblioteki, taką jak Bluebird.

Jeśli używasz węzła 10+, węzeł.js docs zaleca użycie util.promisify, które zwraca obiekt Promise<{ stdout, stderr }>. Zobacz przykład poniżej:

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

Obsługa błędów najpierw z stderr.

 100
Author: shmck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-15 06:22:03

Oto inny sposób:

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

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

Użyj funkcji:

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

Lub z asynchronicznym / oczekującym:

try {
    var result = await execPromise(command);
} catch (e) {
    console.error(e.message);
}
 19
Author: Tomov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-01 09:03:40

Od Node v12 wbudowany util.promisify umożliwia dostęp do obiektu ChildProcess w zwracanym Promise dla wbudowanych funkcji, w których został zwrócony przez nie-promisified wywołanie. Z docs :

Zwracana instancja ChildProcess jest dołączona do Promise jako właściwość child.

To poprawnie i po prostu zaspokaja potrzebę dostępu ChildProcess w pytaniu pierwotnym i sprawia, że Inne odpowiedzi są nieaktualne pod warunkiem, że węzeł v12+ może być używany.

Dostosowując przykład (izwięzły styl ) dostarczony przez pytającego, dostęp do ChildProcess można uzyskać w następujący sposób:

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;
 12
Author: user1823021,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-22 06:05:20

Prawdopodobnie nie ma sposobu, aby zrobić ładnie, który obejmuje wszystkie przypadki użycia. Ale w ograniczonych przypadkach możesz zrobić coś takiego:

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

To akceptuje argumenty opts.stdout i opts.stderr, dzięki czemu stdio może zostać przechwycone z procesu potomnego.

Na przykład:

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

Lub po prostu:

execp('ls ./', {
    stdout: process.stdout,
    stderr: process.stderr
}).then(() => console.log('done!'));
 7
Author: edan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-11 17:02:44

Chcę tylko wspomnieć, że istnieje fajne narzędzie, które całkowicie rozwiąże twój problem:

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

Ten pakiet znacznie ułatwia obsługę procesów.

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

Lub połączyć te funkcje:

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 */);
 5
Author: Tobias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-15 10:13:35

To moje. Nie dotyczy to stdin ani stdout, więc jeśli ich potrzebujesz, użyj jednej z innych odpowiedzi na tej stronie. :)

// 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);
            }
        });
    });
};
 0
Author: Jay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-08 15:43:52