javascript - property - Detener los procesos en ejecución después de que una Promesa sea rechazada
title label html (2)
Estoy usando el siguiente código que funciona bien, pero el problema es que cuando recibo un error, quiero que se detengan todas las demás promesas. Por ejemplo, si chi.getCommand(val1, val2)
enviará un rechazo y llegué a la captura de excepción, quiero cancelar las promesas para chss.exe
y app.getStatus(12);
¿Cómo puedo lograr eso?
var start = Promise.all([
chi.getCommand(val1, val2),
chi.findAndUpdateCustomer()
]).spread(function (command, customer) {
return chss.exe(runnableDoc, command, customer)
.delay(10)
.then(function (val) {
if (val) console.log(val);
return app.getStatus(12);
});
}).catch(function (err) {
// catch and handle errors and when it come to here I want it to stops all the chain above
});
Este es el código del comando get en breve:
function getCommand(method, cmd) {
return new Promise(function (resolve, reject) {
...
child.stderr.on(''data'', function (data) {
console.log(''stderr: here!'' + data);
reject(data);
});
}
La consola de registro stderr: aquí! Se imprimen por lo que se llama la resolución!
ACTUALIZACIÓN1
Lo único que detiene el getStatus es cuando pongo el process.exit(1)
Exita process.exit(1)
Pero esto detiene todo el proceso, solo quiero detener toda la cadena de la función getCommand en caso de que llegue al bloque catch ,
- ¿hay alguna manera?
- ¿Es bug en BlueBird? Yo uso "bluebird": "2.9.34"
función getCommand (método, cmd) {devolver nueva Promesa (función (resolver, rechazar) {
var spawn = require(''child_process'').spawn;
var ls = spawn("cmdbug",["/c","npm install express --save"]);
ls.on(''error'', function (err) {
console.log(err);
reject(err);
});
el error que tengo es
{[Error: spawn cmdr ENOENT] código: ''ENOENT'', errno: ''ENOENT'', syscall: ''spawn cmdbug'', ruta: ''cmdr'', spawnargs: [''/ g'', ''npm install express --save''] } {[Error: spawn cmdbug ENOENT] código: ''ENOENT'', errno: ''ENOENT'', syscall: ''spawn cmdbug'', ruta: ''cmdr'', spawnargs: [''/ g'', ''npm install express --save'' ]} El proceso hijo falló con el código -4058
Y aún el proceso de getStatus se está escribiendo en la consola.
El código que utilizo y no para pruebas es:
El comando getCommand es la función que lanza el error!
var start= function () {
return new Promise.all([
childP.getChildProcessCommand(val1, val2),
childP.findAndUpdateCustomer()
]).spread(function (cmd, updated) {
//Execute child process
return Promise.all([
childP.getCommand(''spawn'', cmd),
app.getStatus(51000,10,1);
]).catch(function (err) {
// catch and handle errors
console.log("An error occur: " + err);
return;
})
}).catch(function (err) {
// catch and handle errors
console.log("An error occur: " + err);
return;
})
}();
El código para verificar el estado es:
// Returns a promise that resolves when the port is open
checkPortStatus: function(port, host){
return new Promise((resolve, reject) => {
portscanner.checkPortStatus(port, host, function(error, status) {
if(error)
reject(error);
else if(status === ''open'')
resolve(status);
else
reject(new Error(''Port is not open''));
});
});
},
// THE API function
getStatus: function(port, retriesLeft) {
const TIME_BETWEEN_CHECKS = 1000;
const HOST = ''127.0.0.1'';
const RETRIES = 20;
retriesLeft = retriesLeft === void 0 ? RETRIES : retriesLeft;
if(!port) throw new Error(''Port is required'');
if(retriesLeft === 0) Promise.reject(''Timed Out'');
return new Promise((resolve, reject) => {
// If it rejects, we do added work.
this.checkPortStatus(port, host).then(resolve, error => {
console.log("Waiting for port " + port + " attempt: " + retry);
setTimeout(() => {
this.getStatus(port, retriesLeft - 1).then(resolve, reject);
}, TIME_BETWEEN_CHECKS);
});
});
}
Y veo el error en la consola y sigo viendo el registro de la consola de los siguientes en 10 intentos. console.log ("Esperando puerto" + puerto + "intento:" + reintentar);
ACTUALIZACIÓN2 Al intentar cambiar Como sugiere @artur en la segunda opción, recibí un error en la llamada recursiva, el error es:
TypeError: no se puede leer la propiedad ''then'' de undefined
Esto es lo que he intentado:
getStatus: function(port, retriesLeft) {
const TIME_BETWEEN_CHECKS = 1000;
const HOST = ''127.0.0.1'';
const RETRIES = 20;
retriesLeft = retriesLeft === void 0 ? RETRIES : retriesLeft;
if(!port) throw new Error(''Port is required'');
if(retriesLeft === 0) Promise.reject(''Timed Out'');
var promise = new Promise((resolve, reject) => {
// If it rejects, we do added work.
this.checkPortStatus(port, host).then(resolve, error => {
console.log("Waiting for port " + port + " attempt: " + retry);
setTimeout(() => {
//The error in the following recursive call
this.getStatus(port, retriesLeft - 1).then(resolve, reject);
}, TIME_BETWEEN_CHECKS);
}).catch(function (error) {
return reject(error);
});
return {
promise:promise,
cancel: function() {
console.log(''cancelling'');
clearTimeout(token);
}
}
});
});
}
Bueno, en su código real (el de UPDATE1) está ejecutando getCommand
simultáneamente para getStatus
, no en secuencia. Está llamando (iniciando) a ambos antes de que falle el proceso hijo, y cuando lo hace, no hay nada que detenga a getStatus
.
Solo getCommand
como en su primer fragmento, donde un rechazo en getCommand
hará que getStatus
no se ejecute en absoluto. Puedes usar
childP.getCommand(''spawn'', cmd)
.timeout(5000)
.then(function(cmdresult) {
return app.getStatus(51000, 10, 1);
}).catch(function (err) {
console.log("An error occured: " + err);
});
Como @Esailija señaló, Bluebird tiene un mecanismo de cancelación incorporado, lo que es realmente bueno y seguro para hacer simples cálculos asíncronos.
Promise.config({
cancellation: true
});
function createCancellableMock(result, time) {
return new Promise(function(resolve, reject, onCancel) {
// var child = runCommand();
var token = setTimeout(function() {
if (result) {
console.log(''almost done'', result);
resolve(result);
} else {
reject(''_ERR_'');
}
}, time);
onCancel(function() {
console.log(''cancelling'');
// child.kill(''SIGTERM'');
clearTimeout(token);
})
})
}
var op1 = createCancellableMock(''ok-1'', 1000);
//var op2 = createCancellableMock(''ok-2'', 500);
var op2 = createCancellableMock(null, 500); // will be rejected
Promise.all([op1, op2])
.spread(function(v1, v2) {
console.log(''BOTH-OK'', v1, v2)
})
.catch(function() {
console.error(''ERROR'');
op1.cancel();
})
.finally(function() {
console.log(''finally'');
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.0/bluebird.core.js"></script>
ACTUALIZAR
Puede cancelar acciones definidas recursivamente (como reintentos). La mejor estrategia en tal caso es no estropear la acción con el comportamiento recursivo. En el siguiente fragmento de código, creé un contenedor muy simple que ilustra mi punto.
var TOO_MANY_RETRIES_ERROR = ''too_many_retries_error'';
var PROB_OF_FAIL = 0.8;
var INTERVAL = 200;
var RETRIES = 5;
var CANCEL_AFTER = null;
//var CANCEL_AFTER = INTERVAL * (RETRIES/2);
Promise.config({
cancellation: true
});
function retryWithCancel(params) {
// params = {op - operation to retry (it should return a promise, which either ),
// interval - between retries, retries - number of retries }
console.log(''running, retries left '', params.retries);
params = Object.assign({}, params); // copy params - no side-effects please
params.retries--;
if (params.retries <= 0) {
console.error(''too many retries'');
return Promise.reject(new Error(TOO_MANY_RETRIES_ERROR));
}
return new Promise(function(resolve, reject, onCancel) {
var o = params.op()
.catch(function() {
return Promise.delay(params.interval)
.then(retryWithCancel.bind(null, params))
.catch(reject)
})
.then(resolve)
onCancel(function() {
console.log(''Cancelling, retries left: '', params.retries);
o.cancel();
});
})
}
function fakeOperation() {
return Promise.delay(100)
.then(function() {
if (Math.random() > PROB_OF_FAIL) {
return Promise.resolve(''SUCCESS'');
} else {
return Promise.reject(new Error(''ERROR''));
}
})
}
var p = retryWithCancel({
op: fakeOperation,
interval: INTERVAL,
retries: RETRIES
})
.then(console.log.bind(console))
.catch(console.error.bind(console))
.finally(console.log.bind(console, ''done''))
if (CANCEL_AFTER) {
setTimeout(function() {
p.cancel();
}, CANCEL_AFTER)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.1/bluebird.js"></script>
RESPUESTA ORIGINAL
En general, las promesas son grandes, pero no ofrecen un mecanismo de cancelación fuera de la caja. Es bastante problemático en algunos escenarios (por ejemplo, https://github.com/whatwg/fetch/issues/27 ) y en su caso, la opción de cancelar también sería bastante útil. La única opción válida es agregarla usted mismo.
solución basada en la promesa básica
Destilé el problema al mínimo y lo convertí en un navegador ejecutable. La desventaja de este enfoque es que después de la cancelación, la promesa nunca se resolve
ni reject
, lo que en general es seguramente unacceptable . Alternativamente, .cancel
puede rechazar la promesa con algún símbolo especial. Ninguno de estos enfoques parece elegante.
function createCancellableMock(result, time) {
// child = null;
var token = null ;
var p = new Promise(function(resolve, reject) {
// child = runCommand();
token = setTimeout(function() {
if (result) {
console.log(''almost done'', result);
resolve(result);
}
else {
reject(''_ERR_'');
}
}, time);
}
)
return {
promise: p,
cancel: function() {
console.log(''cancelling'');
// child.kill(''SIGTERM'');
clearTimeout(token);
}
}
}
var op1 = createCancellableMock(''ok-1'', 1000);
// var op2 = createCancellableMock(''ok-2'', 500);
var op2 = createCancellableMock(null, 500); // will be rejected
Promise.all([op1.promise, op2.promise])
.then(function(vs) { // no spread in native implemantation
console.log(''BOTH-OK'', vs[0], vs[1])
})
.catch(function() {
console.error(''ERROR'');
op1.cancel();
})
solución basada observable
Para la secuencia básica de operaciones, las promesas están bien, pero hay un método disponible más superior: los observables. No solo ofrecen un mecanismo integrado de cancelación / eliminación, sino que también permiten lidiar con múltiples valores emitidos y mantener una ejecución asíncrona sofisticada bajo un control muy estricto.
function createCancellableMock(result, time) {
return Rx.Observable.create(function(observer) {
var done = false;
var token = setTimeout(function() {
if (result) {
console.log(''almost done: '' + result);
observer.onNext(result);
observer.onCompleted();
} else {
observer.onError(''_ERR_'');
}
}, time);
// this will be called upon `disposed`
return function() {
console.log(''disposing, done: '', done);
if (!done) {
clearTimeout(token);
}
}
})
}
var op1 = createCancellableMock(''ok-1'', 1000);
//var op2 = createCancellableMock(''ok-2'', 500);
var op2 = createCancellableMock(null, 500); // will be rejected
op1.zip(op2)
.catch(function(err) {
// it was disposed automatically :) hurray
console.log(''Caught'', err);
// return Rx.Observable.empty(); // swallowing
return Rx.Observable.throw(err); // throwing
})
.subscribe(function(vs) {
console.log(''BOTH-OK'', vs[0], vs[1])
},
function(err) {
console.error(''Unhandled error'', err);
},
function() {
console.log(''Upon successful termination.'')
}
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.js"></script>