javascript - Manejo de excepciones, errores lanzados, dentro de las promesas.
node.js promise (3)
Bloquear y reiniciar un proceso no es una estrategia válida para lidiar con errores, ni siquiera con errores. Estaría bien en Erlang, donde un proceso es barato y hace una cosa aislada, como atender a un solo cliente. Eso no se aplica en el nodo, donde un proceso cuesta más órdenes de magnitud y sirve a miles de clientes a la vez
Digamos que tiene 200 solicitudes por segundo atendidas por su servicio. Si el 1% de ellos alcanzan una ruta de lanzamiento en su código, obtendría 20 paradas de proceso por segundo, aproximadamente una cada 50 ms. Si tiene 4 núcleos con 1 proceso por núcleo, los perdería en 200 ms. Entonces, si un proceso tarda más de 200 ms en iniciarse y prepararse para atender las solicitudes (el costo mínimo es de alrededor de 50 ms para un proceso de nodo que no carga ningún módulo), ahora tenemos una denegación de servicio total exitosa. Sin mencionar que los usuarios que cometen un error tienden a hacer cosas como, por ejemplo, actualizar la página repetidamente, lo que complica el problema.
Los dominios no resuelven el problema porque no pueden garantizar que los recursos no se filtren .
Lea más en los números #5114 y #5149
Sin embargo, las promesas capturan todas las excepciones y luego las propagan de una manera muy similar a la forma en que las excepciones síncronas se propagan en la pila. Además, a menudo proporcionan un método que pretende ser equivalente a un try...finally
gracias a estas dos características, podemos crear "administradores de contexto" (como en python ) que siempre limpian los recursos:
function using(resource, fn) {
// wraps it in case the resource was not promise
var pResource = Promise.cast(resource);
return pResource.then(fn).finally(function() {
return pResource.then(function(resource) {
return resource.dispose();
});
});
}
Entonces úsalos así
function connectAndFetchSomething(...) {
return using(client.connect(host), function(conn) {
var stuff = JSON.parse(something);
return conn.doThings(stuff).then(function(res) {
return conn.doOherThingWith(JSON.parse(res));
));
});
});
Los recursos siempre se desecharán después de que se complete la cadena de promesa dentro del uso del argumento fn
. Incluso si se JSON.parse
un error dentro de esa función (p. Ej., Desde JSON.parse
) o sus cierres .then
(como el segundo JSON.parse
), o si se rechazó una promesa en la cadena (equivalente a devoluciones de llamada que llaman con un error). Por eso es tan importante que las promesas detecten errores y los propaguen.
Edit: Pero, ¿qué hacemos con las bibliotecas que siguen el paradigma de la caída? No podemos asegurarnos de que hayan limpiado sus recursos. ¿Cómo podemos evitar las promesas que subvierten sus excepciones?
Por lo general, estas bibliotecas utilizan devoluciones de llamada de estilo de nodo y, de todos modos, necesitamos envolverlas con promesas. Por ejemplo, podemos tener:
function unwrapped(arg1, arg2, done) {
var resource = allocateResource();
mayThrowError1();
resource.doesntThrow(arg1, function(err, res) {
mayThrowError2(arg2);
done(err, res);
});
}
mayThrowError2()
está dentro de una devolución de llamada interna y aún así se bloqueará el proceso si se lanza, incluso si se llama sin .then
dentro de otra promesa .then
Sin embargo, mayThrowError1()
será atrapado por la promesa si se llama dentro de .then
, y el recurso asignado interno se perderá.
Podemos ajustar esta función de manera que nos aseguremos de que no se puedan recuperar los errores que se producen y bloquear el proceso:
function wrapped(arg1, arg2) {
var defer = Promise.pending();
try {
unwrapped(arg1, arg2, function callback(err, res) {
if (err) defer.reject(err);
else defer.fulfill(res);
});
} catch (e) {
process.nextTick(function rethrow() { throw e; });
}
}
El uso de la función envuelta dentro de otra promesa. .then
devolución de llamada se produce con un bloqueo del proceso si los lanzamientos sin .then
caen de nuevo al paradigma de la caída.
Es la esperanza general de que a medida que use más y más bibliotecas basadas en promesas, usarán el patrón del administrador de contexto para administrar sus recursos y, por lo tanto, tendrían menos necesidad de dejar que el proceso se bloquee.
Ninguna de estas soluciones es a prueba de balas, ni siquiera falla en los errores lanzados. Es muy fácil escribir accidentalmente el código que pierde recursos a pesar de no tirar. Por ejemplo, esta función de estilo de nodo perderá recursos aunque no arroje:
function unwrapped(arg1, arg2, done) {
var resource = allocateResource();
resource.doSomething(arg1, function(err, res) {
if (err) return done(err);
resource.doSomethingElse(res, function(err, res) {
resource.dispose();
done(err, res);
});
});
}
¿Por qué? Porque cuando la devolución de llamada de doSomething recibe un error, el código se olvida de deshacerse del recurso.
Este tipo de problema no ocurre con los gestores de contexto. No puede olvidar llamar a disposición: no tiene que hacerlo, ¡ya que using
hace por usted!
Referencias: por qué estoy cambiando a promesas , gestores de contexto y transacciones
Estoy ejecutando código externo como una extensión de terceros para un servicio de node.js. Los métodos API devuelven promesas. Una promesa resuelta significa que la acción se llevó a cabo con éxito, una promesa fallida significa que hubo algún problema para llevar a cabo la operación.
Ahora aquí es donde estoy teniendo problemas.
Dado que el código de terceros es desconocido, podría haber errores, errores de sintaxis, problemas de tipo, cualquier cantidad de cosas que podrían hacer que node.js produzca una excepción.
Sin embargo, dado que todo el código está envuelto en promesas, estas excepciones lanzadas están regresando como promesas fallidas.
Intenté colocar la llamada de función dentro de un bloque try / catch, pero nunca se activó:
// worker process
var mod = require(''./3rdparty/module.js'');
try {
mod.run().then(function (data) {
sendToClient(true, data);
}, function (err) {
sendToClient(false, err);
});
} catch (e) {
// unrecoverable error inside of module
// ... send signal to restart this worker process ...
});
En el ejemplo anterior de código de psuedo, cuando se produce un error, aparece en la función de promesa fallida y no en la captura.
Por lo que leí, esta es una característica, no un problema, con promesas. Sin embargo, estoy teniendo problemas para comprender por qué siempre querría tratar las excepciones y los rechazos esperados de la misma manera.
Un caso se trata de errores reales en el código, posiblemente irrecuperables: el otro es simplemente la información de configuración que falta, o un parámetro, o algo recuperable.
¡Gracias por cualquier ayuda!
El rechazo de la promesa es simplemente una abstracción del fracaso. También lo son las devoluciones de llamada de estilo de nodo (err, res) y las excepciones. Como las promesas son asíncronas, no se puede usar try-catch para capturar nada, ya que es probable que ocurran errores no en el mismo tick del ciclo de eventos.
Un ejemplo rápido:
function test(callback){
throw ''error'';
callback(null);
}
try {
test(function () {});
} catch (e) {
console.log(''Caught: '' + e);
}
Aquí podemos detectar un error, ya que la función es síncrona (aunque está basada en la devolución de llamada). Otro:
function test(callback){
process.nextTick(function () {
throw ''error'';
callback(null);
});
}
try {
test(function () {});
} catch (e) {
console.log(''Caught: '' + e);
}
¡Ahora no podemos atrapar el error! La única opción es pasarlo en la devolución de llamada:
function test(callback){
process.nextTick(function () {
callback(''error'', null);
});
}
test(function (err, res) {
if (err) return console.log(''Caught: '' + err);
});
Ahora funciona igual que en el primer ejemplo. Lo mismo se aplica a las promesas: no puede usar try-catch, así que usa rechazos para el manejo de errores.
Es casi la característica más importante de las promesas. Si no estuviera allí, también podría usar devoluciones de llamada:
var fs = require("fs");
fs.readFile("myfile.json", function(err, contents) {
if( err ) {
console.error("Cannot read file");
}
else {
try {
var result = JSON.parse(contents);
console.log(result);
}
catch(e) {
console.error("Invalid json");
}
}
});
(Antes de decir que JSON.parse
es lo único que lanza en js, ¿sabías que incluso al forzar una variable a un número, por ejemplo, +a
puede lanzar un TypeError
?
Sin embargo, el código anterior se puede expresar mucho más claramente con promesas porque solo hay un canal de excepción en lugar de 2:
var Promise = require("bluebird");
var readFile = Promise.promisify(require("fs").readFile);
readFile("myfile.json").then(JSON.parse).then(function(result){
console.log(result);
}).catch(SyntaxError, function(e){
console.error("Invalid json");
}).catch(function(e){
console.error("Cannot read file");
});
Tenga en cuenta que la catch
es azúcar para .then(null, fn)
. Si entiendes cómo funciona el flujo de excepciones, verás que es una .then(fnSuccess, fnFail)
para usar generalmente .then(fnSuccess, fnFail)
.
El punto no tiene nada que ver .then(success, fail)
over , function(fail, success)
(IE no es una forma alternativa de adjuntar sus devoluciones de llamada) pero hace que el código escrito se vea casi igual al que se vería al escribir código síncrono:
try {
var result = JSON.parse(readFileSync("myjson.json"));
console.log(result);
}
catch(SyntaxError e) {
console.error("Invalid json");
}
catch(Error e) {
console.error("Cannot read file");
}
(El código de sincronización realmente será más feo en realidad porque javascript no tiene capturas escritas)