javascript - ¿Cómo promisificar las funciones child_process.exec y child_process.execFile de Node con Bluebird?
Estoy usando la biblioteca de promesas Bluebird bajo Node.js, ¡es genial! Pero tengo una pregunta:
Si echas un vistazo a la documentación de child_process.exec y child_process.execFile de Node, puedes ver que ambas funciones devuelven un objeto ChildProcess.
Entonces, ¿cuál es la forma recomendada de promisificar tales funciones?
Tenga en cuenta que los siguientes funcionan (obtengo un objeto Promise):
var Promise = require('bluebird');
var execAsync = Promise.promisify(require('child_process').exec);
var execFileAsync = Promise.promisify(require('child_process').execFile);
¿Pero cómo se puede obtener acceso al valor de retorno original de las funciones originales de Node.js? (En estos casos necesitaría poder acceder a los objetos ChildProcess que se retornaron originalmente.)
¡Cualquier sugerencia sería apreciada!
EDITAR :
Aquí hay un ejemplo de código que está utilizando el valor de retorno de la función 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);
});
Pero si usara la versión promisificada de la función exec (execAsync de arriba) entonces el valor de retorno será una promesa, no un objeto ChildProcess. Este es el verdadero problema del que estoy hablando.
Respuesta #1
Recomendaría usar las promesas estándar de JS integradas en el lenguaje en lugar de una dependencia de biblioteca adicional como Bluebird.
Si estás usando Node 10 o superior, la documentación de Node.js recomienda usar util.promisify, que devuelve un objeto Promise<{ stdout, stderr }>. Ve un ejemplo a continuación:
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()
Maneja primero los errores desde stderr.
Respuesta #2
Parece que te gustaría devolver dos cosas de la llamada:
- el ChildProcess
- una promesa que se resuelve cuando el ChildProcess se completa
¿Entonces "la forma recomendada de promisificar tales funciones"? No.
Estás fuera de la convención. Se espera que las funciones que devuelven promesas retornen una promesa, y eso es todo. Podrías devolver un objeto con dos miembros (el ChildProcess y la promesa), pero eso solo confundirá a la gente.
Sugiero llamar a la función que no devuelve promesas y crear una promesa basada en el childProcess devuelto. (Tal vez envolver eso en una función auxiliar)
De esta manera, es bastante explícito para la próxima persona que lea el código.
Algo como:
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);
});
Si solo quieres convertir en promesas específicamente child_process.exec() y child_process.execFile(), en las versiones recientes de Node hay una mejor solución aquí.
Respuesta #3
Desde Node v12, el util.promisify incorporado permite acceder al objeto ChildProcess en el Promise devuelto para funciones integradas donde habría sido devuelto por la llamada no promisificada. Según la documentación:
La instancia
ChildProcessdevuelta está adjunta alPromisecomo una propiedadchild.
Esto satisface correcta y simplemente la necesidad de acceder a ChildProcess en la pregunta original y hace que otras respuestas queden desactualizadas, siempre que se pueda usar Node v12+.
Adaptando el ejemplo (y el estilo conciso) proporcionado por quien formula la pregunta, el acceso al ChildProcess se puede lograr así:
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;
Respuesta #4
Aquí hay otra manera:
function execPromise(command) {
return new Promise(function(resolve, reject) {
exec(command, (error, stdout, stderr) => {
if (error) {
reject(error);
return;
}
resolve(stdout.trim());
});
});
}
Usa la función:
execPromise(command).then(function(result) {
console.log(result);
}).catch(function(e) {
console.error(e.message);
});
O con async/await:
try {
var result = await execPromise(command);
} catch (e) {
console.error(e.message);
}
