Después de de una prolongada compilación de información hemos podido solucionar esta contrariedad que tienen ciertos usuarios. Te ofrecemos la solución y esperamos servirte de mucha apoyo.
Solución:
Actualización: Joyent ahora tiene su propia guía. La siguiente información es más un resumen:
Errores de “lanzamiento” de forma segura
Idealmente, nos gustaría evitar errores no detectados tanto como sea posible, como tal, en lugar de lanzar literalmente el error, podemos “lanzar” el error de manera segura usando uno de los siguientes métodos dependiendo de nuestra arquitectura de código:
-
Para el código síncrono, si ocurre un error, devuelva el error:
// Define divider as a syncrhonous function var divideSync = function(x,y) // if error condition? if ( y === 0 ) // "throw" the error safely by returning it return new Error("Can't divide by zero") else // no error occured, continue on return x/y // Divide 4/2 var result = divideSync(4,2) // did an error occur? if ( result instanceof Error ) // handle the error safely console.log('4/2=err', result) else // no error occured, continue on console.log('4/2='+result) // Divide 4/0 result = divideSync(4,0) // did an error occur? if ( result instanceof Error ) // handle the error safely console.log('4/0=err', result) else // no error occured, continue on console.log('4/0='+result)
-
Para el código basado en devolución de llamada (es decir, asíncrono), el primer argumento de la devolución de llamada es
err
, si ocurre un errorerr
es el error, si no ocurre un error, entonceserr
esnull
. Cualquier otro argumento sigue elerr
argumento:var divide = function(x,y,next) // if error condition? if ( y === 0 ) // "throw" the error safely by calling the completion callback // with the first argument being the error next(new Error("Can't divide by zero")) else // no error occured, continue on next(null, x/y) divide(4,2,function(err,result) // did an error occur? if ( err ) // handle the error safely console.log('4/2=err', err) else // no error occured, continue on console.log('4/2='+result) ) divide(4,0,function(err,result) // did an error occur? if ( err ) // handle the error safely console.log('4/0=err', err) else // no error occured, continue on console.log('4/0='+result) )
-
Para código accidentado, donde el error puede ocurrir en cualquier lugar, en lugar de lanzar el error, active el
error
evento en su lugar:// Definite our Divider Event Emitter var events = require('events') var Divider = function() events.EventEmitter.call(this) require('util').inherits(Divider, events.EventEmitter) // Add the divide function Divider.prototype.divide = function(x,y) // if error condition? if ( y === 0 ) // "throw" the error safely by emitting it var err = new Error("Can't divide by zero") this.emit('error', err) else // no error occured, continue on this.emit('divided', x, y, x/y) // Chain return this; // Create our divider and listen for errors var divider = new Divider() divider.on('error', function(err) // handle the error safely console.log(err) ) divider.on('divided', function(x,y,result) console.log(x+'/'+y+'='+result) ) // Divide divider.divide(4,2).divide(4,0)
“Detectar” errores de forma segura
A veces, sin embargo, todavía puede haber código que arroja un error en algún lugar que puede conducir a una excepción no detectada y un posible bloqueo de nuestra aplicación si no lo detectamos de manera segura. Dependiendo de nuestra arquitectura de código, podemos usar uno de los siguientes métodos para capturarlo:
-
Cuando sabemos dónde está ocurriendo el error, podemos envolver esa sección en un dominio node.js
var d = require('domain').create() d.on('error', function(err) // handle the error safely console.log(err) ) // catch the uncaught errors in this asynchronous or synchronous code block d.run(function() // the asynchronous or synchronous code that we want to catch thrown errors on var err = new Error('example') throw err )
-
Si sabemos dónde está ocurriendo el error es el código síncrono, y por alguna razón no podemos usar dominios (quizás la versión anterior del nodo), podemos usar la declaración try catch:
// catch the uncaught errors in this synchronous code block // try catch statements only work on synchronous code try // the synchronous code that we want to catch thrown errors on var err = new Error('example') throw err catch (err) // handle the error safely console.log(err)
Sin embargo, tenga cuidado de no utilizar
try...catch
en código asincrónico, ya que no se detectará un error lanzado de forma asincrónica:try setTimeout(function() var err = new Error('example') throw err , 1000) catch (err) // Example error won't be caught here... crashing our app // hence the need for domains
Si quieres trabajar con
try..catch
junto con el código asincrónico, al ejecutar Node 7.4 o superior, puede usarasync/await
de forma nativa para escribir sus funciones asincrónicas.Otra cosa con la que tener cuidado
try...catch
es el riesgo de envolver su devolución de llamada de finalización dentro deltry
declaración así:var divide = function(x,y,next) // if error condition? if ( y === 0 ) // "throw" the error safely by calling the completion callback // with the first argument being the error next(new Error("Can't divide by zero")) else // no error occured, continue on next(null, x/y) var continueElsewhere = function(err, result) throw new Error('elsewhere has failed') try divide(4, 2, continueElsewhere) // ^ the execution of divide, and the execution of // continueElsewhere will be inside the try statement catch (err) console.log(err.stack) // ^ will output the "unexpected" result of: elsewhere has failed
Este problema es muy fácil de hacer a medida que su código se vuelve más complejo. Como tal, es mejor usar dominios o devolver errores para evitar (1) excepciones no detectadas en código asincrónico (2) el intento de capturar la ejecución de captura que no desea. En los lenguajes que permiten un subproceso adecuado en lugar del estilo de máquina de eventos asincrónica de JavaScript, esto es un problema menor.
-
Finalmente, en el caso de que ocurra un error no detectado en un lugar que no estaba incluido en un dominio o una declaración try catch, podemos hacer que nuestra aplicación no se bloquee usando el
uncaughtException
oyente (sin embargo, hacerlo puede poner la aplicación en un estado desconocido):// catch the uncaught errors that weren't wrapped in a domain or try catch statement // do not use this in modules, but only in applications, as otherwise we could have multiple of these bound process.on('uncaughtException', function(err) // handle the error safely console.log(err) ) // the asynchronous or synchronous code that emits the otherwise uncaught error var err = new Error('example') throw err
A continuación se incluye un resumen y una selección de muchas fuentes diferentes sobre este tema, incluidos ejemplos de código y citas de publicaciones de blog seleccionadas. La lista completa de mejores prácticas se puede encontrar aquí.
Mejores prácticas de manejo de errores de Node.JS
Número1: use promesas para el manejo de errores asíncronos
TL; DR: Manejar los errores asíncronos en el estilo de devolución de llamada es probablemente el camino más rápido al infierno (también conocido como la pirámide de la fatalidad). El mejor regalo que puede darle a su código es utilizar en su lugar una biblioteca de promesas de buena reputación que proporcione una sintaxis de código muy compacta y familiar, como try-catch
De lo contrario: El estilo de devolución de llamada de Node.JS, función (err, respuesta), es una forma prometedora de código no mantenible debido a la combinación de manejo de errores con código casual, anidación excesiva y patrones de codificación incómodos
Ejemplo de código: bueno
doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);
ejemplo de código anti patrón – manejo de errores de estilo de devolución de llamada
getData(someParameter, function(err, result)
if(err != null)
//do something like calling the given callback function and pass the error
getMoreData(a, function(err, result)
if(err != null)
//do something like calling the given callback function and pass the error
getMoreData(b, function(c)
getMoreData(d, function(e)
...
);
);
);
);
});
Cita del blog: “Tenemos un problema con las promesas”
(Del blog pouchdb, clasificado 11 para las palabras clave “Promesas de nodo”)
“… Y, de hecho, las devoluciones de llamada hacen algo aún más siniestro: nos privan de la pila, que es algo que generalmente damos por sentado en los lenguajes de programación. Escribir código sin una pila es muy parecido a conducir un automóvil sin un pedal de freno: no te das cuenta de lo mucho que lo necesitas, hasta que lo alcanzas y no está allí. El objetivo de las promesas es devolvernos los fundamentos del lenguaje que perdimos cuando nos volvimos asíncronos: retorno, lanzamiento y la pila. Pero hay que saber utilizar correctamente las promesas para aprovecharlas.“
Número2: use solo el objeto Error incorporado
TL; DR: Es bastante común ver código que arroja errores como una cadena o como un tipo personalizado; esto complica la lógica de manejo de errores y la interoperabilidad entre módulos. Ya sea que rechace una promesa, arroje una excepción o emita un error, el uso del objeto Error incorporado de Node.JS aumenta la uniformidad y evita la pérdida de información de error
De lo contrario: Al ejecutar algún módulo, no estar seguro del tipo de errores que se producen a cambio hace que sea mucho más difícil razonar sobre la próxima excepción y manejarla. Incluso vale la pena, el uso de tipos personalizados para describir errores podría conducir a la pérdida de información crítica de errores como el seguimiento de la pila.
Ejemplo de código: hacerlo bien
//throwing an Error from typical function, whether sync or async
if(!productToAdd)
throw new Error("How can I add new product when no value provided?");
//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
//'throwing' an Error from a Promise
return new promise(function (resolve, reject) {
DAL.getProduct(productToAdd.id).then((existingProduct) =>{
if(existingProduct != null)
return reject(new Error("Why fooling us and trying to add an existing product?"));
ejemplo de código anti patrón
//throwing a String lacks any stack trace information and other important properties
if(!productToAdd)
throw ("How can I add new product when no value provided?");
Cita del blog: “Una cadena no es un error”
(Del blog devthought, clasificado 6 para las palabras clave “Objeto de error de Node.JS”)
“… pasar una cadena en lugar de un error da como resultado una menor interoperabilidad entre los módulos. Rompe los contratos con las API que podrían estar realizando una instancia de comprobaciones de errores o que quieran saber más sobre el error. Los objetos de error, como veremos, tienen propiedades muy interesantes en los motores JavaScript modernos además de contener el mensaje pasado al constructor .. “
Número 3: distinguir errores operativos frente a errores del programador
TL; DR: Los errores de operaciones (por ejemplo, la API recibió una entrada no válida) se refieren a casos conocidos en los que el impacto del error se comprende completamente y se puede manejar con cuidado. Por otro lado, el error del programador (por ejemplo, al intentar leer una variable indefinida) se refiere a fallas de código desconocido que exigen reiniciar correctamente la aplicación.
De lo contrario: Siempre puede reiniciar la aplicación cuando aparezca un error, pero ¿por qué dejar que ~ 5000 usuarios en línea se desconecten debido a un error menor y previsto (error operativo)? lo contrario tampoco es lo ideal: mantener la aplicación activa cuando se produce un problema desconocido (error del programador) puede generar un comportamiento impredecible. Diferenciar los dos permite actuar con tacto y aplicar un enfoque equilibrado en función del contexto dado.
Ejemplo de código: hacerlo bien
//throwing an Error from typical function, whether sync or async
if(!productToAdd)
throw new Error("How can I add new product when no value provided?");
//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
//'throwing' an Error from a Promise
return new promise(function (resolve, reject) {
DAL.getProduct(productToAdd.id).then((existingProduct) =>{
if(existingProduct != null)
return reject(new Error("Why fooling us and trying to add an existing product?"));
ejemplo de código: marcar un error como operativo (confiable)
//marking an error object as operational
var myError = new Error("How can I add new product when no value provided?");
myError.isOperational = true;
//or if you're using some centralized error factory (see other examples at the bullet "Use only the built-in Error object")
function appError(commonType, description, isOperational)
Error.call(this);
Error.captureStackTrace(this);
this.commonType = commonType;
this.description = description;
this.isOperational = isOperational;
;
throw new appError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true);
//error handling code within middleware
process.on('uncaughtException', function(error)
if(!error.isOperational)
process.exit(1);
);
Cotización del blog: “De lo contrario, arriesga el estado” (Del blog debugable, clasificado 3 para las palabras clave “Excepción no detectada de Node.JS”)
“… Por la naturaleza misma de cómo funciona throw en JavaScript, casi nunca hay forma de “retomar donde lo dejaste” de forma segura, sin filtrar referencias o crear algún otro tipo de estado frágil indefinido. La forma más segura de responder a un error arrojado es cerrar el proceso.. Por supuesto, en un servidor web normal, es posible que tenga muchas conexiones abiertas, y no es razonable cerrarlas abruptamente porque alguien más provocó un error. El mejor enfoque es enviar una respuesta de error a la solicitud que desencadenó el error, mientras deja que los demás terminen en su tiempo normal y dejen de escuchar nuevas solicitudes en ese trabajador “
Número 4: gestione los errores de forma centralizada, a través de middleware, pero no dentro de él.
TL; DR: La lógica de manejo de errores, como el correo al administrador y el registro, debe encapsularse en un objeto dedicado y centralizado al que todos los puntos finales (p. Ej., Middleware Express, trabajos cron, pruebas unitarias) llaman cuando aparece un error.
De lo contrario: No manejar los errores en un solo lugar conducirá a la duplicación del código y probablemente a errores que se manejen de manera incorrecta.
Ejemplo de código: un flujo de error típico
//DAL layer, we don't handle errors here
DB.addDocument(newCustomer, (error, result) =>
if (error)
throw new Error("Great error explanation comes here", other useful parameters)
);
//API route code, we catch both sync and async errors and forward to the middleware
try
customerService.addNew(req.body).then(function (result)
res.status(200).json(result);
).catch((error) =>
next(error)
);
catch (error)
next(error);
//Error handling middleware, we delegate the handling to the centrzlied error handler
app.use(function (err, req, res, next)
errorHandler.handleError(err).then((isOperationalError) =>
if (!isOperationalError)
next(err);
);
);
Cita del blog: “A veces, los niveles más bajos no pueden hacer nada útil excepto propagar el error a la persona que llama” (Del blog Joyent, clasificado 1 por las palabras clave “Manejo de errores de Node.JS”)
“… Puede terminar manejando el mismo error en varios niveles de la pila. Esto sucede cuando los niveles inferiores no pueden hacer nada útil excepto propagar el error a su llamador, que propaga el error a su llamador, y así sucesivamente. A menudo, solo la persona que llama de nivel superior sabe cuál es la respuesta adecuada, ya sea para volver a intentar la operación, informar un error al usuario o cualquier otra cosa. Pero eso no significa que deba intentar informar todos los errores a un solo nivel superior devolución de llamada, porque esa devolución de llamada en sí misma no puede saber en qué contexto ocurrió el error “
Número 5: errores de la API de documentos al usar Swagger
TL; DR: Informe a las personas que llaman a la API qué errores pueden surgir a cambio para que puedan manejarlos cuidadosamente sin fallar. Esto generalmente se hace con marcos de documentación de API REST como Swagger
De lo contrario: Un cliente de API puede decidir fallar y reiniciar solo porque recibió un error que no pudo entender. Nota: la persona que llama a su API puede ser usted (muy típico en un entorno de microservicios)
Cita del blog: “Tienes que decirles a las personas que llaman qué errores pueden ocurrir” (Del blog Joyent, clasificado 1 por las palabras clave “Registro de Node.JS”)
… Hemos hablado sobre cómo manejar los errores, pero cuando estás escribiendo una nueva función, ¿cómo entregas errores al código que llamó a tu función? … Si no sabe qué errores pueden ocurrir o no sabe lo que significan, entonces su programa no puede ser correcto excepto por accidente. Entonces, si está escribiendo una nueva función, debe decirles a las personas que llaman qué errores pueden ocurrir y qué significan.
Número 6: Cierra el proceso con gracia cuando un extraño llega a la ciudad.
TL; DR: Cuando se produce un error desconocido (un error del desarrollador, consulte la práctica recomendada número 3), existe incertidumbre sobre el estado de la aplicación. Una práctica común sugiere reiniciar el proceso con cuidado utilizando una herramienta de ‘reinicio’ como Forever y PM2
De lo contrario: Cuando se detecta una excepción desconocida, algún objeto puede estar en un estado defectuoso (por ejemplo, un emisor de eventos que se usa globalmente y ya no dispara eventos debido a alguna falla interna) y todas las solicitudes futuras pueden fallar o comportarse de manera loca
Ejemplo de código: decidir si chocar
//deciding whether to crash when an uncaught exception arrives
//Assuming developers mark known operational errors with error.isOperational=true, read best practice #3
process.on('uncaughtException', function(error)
errorManagement.handler.handleError(error);
if(!errorManagement.handler.isTrustedError(error))
process.exit(1)
);
//centralized error handler encapsulates error-handling related logic
function errorHandler(){
this.handleError = function (error)
return logger.logError(err).then(sendMailToAdminIfCritical).then(saveInOpsQueueIfCritical).then(determineIfOperationalError);
this.isTrustedError = function(error)
return error.isOperational;
Cita del blog: “Hay tres escuelas de pensamiento sobre el manejo de errores” (Del blog jsrecipes)
… Hay principalmente tres escuelas de pensamiento sobre el manejo de errores: 1. Deje que la aplicación se bloquee y reiníciela. 2. Maneje todos los errores posibles y nunca se bloquee. 3. Enfoque equilibrado entre los dos
Número 7: use un registrador maduro para aumentar la visibilidad de errores
TL; DR: Un conjunto de herramientas de registro maduras, como Winston, Bunyan o Log4J, acelerará el descubrimiento y la comprensión de errores. Así que olvídate de console.log.
De lo contrario: Hojear console.logs o manualmente a través de un archivo de texto desordenado sin herramientas de consulta o un visor de registro decente puede mantenerte ocupado en el trabajo hasta tarde
Ejemplo de código: Winston logger en acción
//your centralized logger object
var logger = new winston.Logger(
level: 'info',
transports: [
new (winston.transports.Console)(),
new (winston.transports.File)( filename: 'somefile.log' )
]
);
//custom code somewhere using the logger
logger.log('info', 'Test Log Message with some parameter %s', 'some parameter', anything: 'This is metadata' );
Cita del blog: “Identifiquemos algunos requisitos (para un registrador):” (Del blog strongblog)
… Identifiquemos algunos requisitos (para un registrador): 1. Marque la hora de cada línea de registro. Este es bastante autoexplicativo: debería poder saber cuándo ocurrió cada entrada de registro. 2. El formato de registro debe ser fácilmente digerible tanto por humanos como por máquinas. 3. Permite múltiples flujos de destino configurables. Por ejemplo, es posible que esté escribiendo registros de seguimiento en un archivo, pero cuando se encuentre un error, escriba en el mismo archivo, luego en el archivo de error y envíe un correo electrónico al mismo tiempo …
Número 8: Descubra errores y tiempo de inactividad con productos APM
TL; DR: Los productos de supervisión y rendimiento (también conocidos como APM) miden de forma proactiva su base de código o API para que puedan resaltar automáticamente y por arte de magia los errores, los fallos y las piezas lentas que faltaban.
De lo contrario: Es posible que dedique un gran esfuerzo a medir el rendimiento de la API y los tiempos de inactividad, probablemente nunca se dará cuenta de cuáles son sus partes de código más lentas en un escenario del mundo real y cómo afectan a la UX
Cita del blog: “Segmentos de productos APM” (del blog Yoni Goldberg)
“… Los productos APM constituyen 3 segmentos principales:1. Monitoreo de sitios web o API – servicios externos que monitorean constantemente el tiempo de actividad y el rendimiento a través de solicitudes HTTP. Se puede configurar en pocos minutos. A continuación se muestran algunos contendientes seleccionados: Pingdom, Uptime Robot y New Relic
2. Instrumentación de código – familia de productos que requieren integrar un agente dentro de la aplicación para beneficiarse de la función de detección lenta de código, estadísticas de excepciones, monitoreo del rendimiento y muchos más. A continuación se muestran algunos contendientes seleccionados: New Relic, App Dynamics
3. Panel de inteligencia operativa – esta línea de productos se centra en facilitar al equipo de operaciones con métricas y contenido seleccionado que ayuda a mantenerse al tanto del rendimiento de la aplicación. Por lo general, esto implica la agregación de múltiples fuentes de información (registros de aplicaciones, registros de bases de datos, registros de servidores, etc.) y el trabajo de diseño del tablero por adelantado. A continuación se muestran algunos candidatos seleccionados: Datadog, Splunk “
La anterior es una versión abreviada; consulte aquí más prácticas recomendadas y ejemplos
Puede detectar excepciones no detectadas, pero es de uso limitado. Ver http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb
monit
, forever
o upstart
se puede utilizar para reiniciar el proceso del nodo cuando falla. Lo mejor que puede esperar es un cierre ordenado (por ejemplo, guardar todos los datos en memoria en un controlador de excepciones no detectado).
Sección de Reseñas y Valoraciones
Recuerda dar visibilidad a este artículo si si solucionó tu problema.