El manejo de errores es un dolor, y es fácil subsistir durante mucho tiempo el error node.js sin tratar los errores correctamente. Sin embargo, la creación de aplicaciones robustas de Node.js requiere lidiar con los errores correctamente, y no es difícil aprender cómo hacerlo. Si está realmente impaciente, vaya a la sección “Resumen” para ver un tl; dr.

Este documento responderá varias preguntas que los programadores nuevos en Node.js a menudo hacen el error node.js:

  • En las funciones que escribo, ¿ throwcuándo debo emitir un error y cuándo debo emitirlo con una devolución de llamada, un emisor de eventos o algo más?
  • ¿Qué deben asumir mis funciones sobre sus argumentos? ¿Debo comprobar que son los tipos correctos? ¿Debo verificar restricciones más específicas, como que un argumento no es nulo, no es negativo, se parece a una dirección IP o similar?
  • ¿Cómo debo tratar con los argumentos que no coinciden con lo que espera la función? ¿Debo lanzar una excepción o emitir un error a la devolución de llamada?
  • ¿Cómo puedo distinguir entre diferentes tipos de errores mediante programación (p. Ej., Un error de “Solicitud incorrecta” frente a un error de “Servicio no disponible”)?
  • ¿Cómo puedo proporcionar suficientes detalles con mis errores para que las personas que llaman puedan saber qué hacer al respecto?
  • ¿Cómo debo manejar los errores inesperados? ¿Debo usar trycatch, dominios, o algo más?

Este documento está dividido en varias partes que se construyen unas sobre otras:

  • Antecedentes : lo que se espera que sepas ya.
  • Errores operativos frente a errores de programación (error node.js ): introducción a dos tipos de errores fundamentalmente diferentes
  • Patrones para escribir funciones : principios generales para escribir funciones que producen errores útiles
  • Recomendaciones específicas para escribir nuevas funciones : una lista de verificación de pautas específicas para escribir funciones robustas que producen errores útiles
  • Un ejemplo : documentación de ejemplo y preámbulo para una connectfunción.
  • Resumen : un resumen de todo hasta este punto.
  • Apéndice: Propiedades convencionales para los objetos de error : una lista de nombres de propiedades para usar para proporcionar información adicional de manera estándar

Fondo error node.js

Este documento asume:

  • Usted está familiarizado con la idea de excepciones en JavaScript, Java, Python, C ++, o cualquier lenguaje similar, y que sabe lo que significa throwcatchellos.
  • Estás familiarizado con la programación en Node.js. Se siente cómodo con las operaciones asíncronas y con el callback(err, result)patrón de completar una operación asíncrona.
  • Debes saber por qué este patrón:
function myApiFunc(callback) {
  /*
   * This pattern does NOT work!
   */
  try {
    doSomeAsynchronousOperation((err) => {
      if (err) {
        throw (err);
      }
      /* continue as normal */
    });
  } catch (ex) {
    callback(ex);
  }
}

No funciona para manejar el error node.js

También debe estar familiarizado con las cuatro formas principales de entregar un error node.js

  • throwEl error (por lo que es una excepción ).
  • pasar el error a una devolución de llamada , una función proporcionada específicamente para manejar errores y los resultados de operaciones asíncronas
  • pasar el error a una función de promesa de rechazo
  • emitir un "error"evento en un EventEmitter

Discutiremos cuándo usar cada uno de estos patrones a continuación. Este documento no asume que sabes algo acerca de los dominios.

Finalmente, debes saber que en JavaScript (y en error node.js especialmente), hay una diferencia entre un error y una excepción. Un error es cualquier instancia de la Errorclase. Los errores pueden ser construidos y luego pasados directamente a otra función o lanzados. Cuando se throwproduce un error, se convierte en una excepción. Aquí hay un ejemplo de cómo usar un error como excepción:

throw new Error('something bad happened');

pero también puedes crear un Errorsin lanzarlo:

callback(new Error('something bad happened'));

y esto es mucho más común en el error node.js porque la mayoría de los errores son asíncronos. Como veremos, es muy raro que se necesite catchun error de una función síncrona. Esto es muy diferente a Java, C ++ y otros lenguajes que hacen un uso intensivo de las excepciones.

Errores operacionales vs. errores del programador

Es útil dividir todos los errores en dos categorías amplias:

  • Los errores operativos

    representan problemas de tiempo de ejecución experimentados por programas escritos correctamente. Estos no son errores en el programa. De hecho, estos suelen ser problemas con otra cosa: el sistema en sí (p. Ej., Sin memoria o demasiados archivos abiertos), la configuración del sistema (p. Ej., Sin ruta a un host remoto), la red (p. Ej., Conexión de socket) ), o un servicio remoto (por ejemplo, un error 500, falla de conexión o similar). Ejemplos incluyen:

    • Error al conectar con el servidor
    • No se pudo resolver el nombre de host
    • entrada de usuario inválida
    • pide tiempo fuera
    • servidor devolvió una respuesta 500
    • zócalo colgar
    • el sistema no tiene memoria
  • Los errores del programador

    son errores en el programa. Estas son cosas que siempre se pueden evitar cambiando el código. Nunca se pueden manejar adecuadamente (ya que, por definición, el código en cuestión está roto).

    • Intenté leer propiedad de “indefinido”
    • llamada una función asíncrona sin devolución de llamada
    • pasó una “cadena” donde se esperaba un objeto
    • pasó un objeto donde se esperaba una cadena de dirección IP

La gente usa el término “errores” para hablar de errores operativos y de programación, pero son realmente muy diferentes. Los errores operacionales son condiciones de error con las que deben lidiar todos los programas correctos, y mientras se tratan, no necesariamente indican un error o incluso un problema grave. “Archivo no encontrado” es un error operacional, pero no significa necesariamente que algo esté mal. Podría significar que el programa tiene que crear primero el archivo que está buscando.

Por el contrario, los errores del programador son errores. Son casos en los que cometió un error, tal vez olvidando validar la entrada del usuario, escribiendo mal el nombre de una variable o algo así. Por definición no hay manera de manejar eso. Si lo hubiera, ¡habría usado el código de manejo de errores en lugar del código que causó el error!

Esta distinción es muy importante: los errores operacionales son parte del funcionamiento normal de un programa. Los errores de programación son errores.

A veces, tiene errores operativos y de programación como parte del mismo problema raíz. Si un servidor HTTP intenta usar una variable no definida y se bloquea, se trata de un error del programador. Todos los clientes con solicitudes en vuelo en el momento del accidente verán un ECONNRESETerror, que normalmente se informa en Node como un “bloqueo de socket”. Para el cliente, eso es un error operacional separado. Esto se debe a que un cliente correcto debe manejar un servidor que falla o una red que no funciona.

De manera similar, la falla en el manejo de un error operacional es en sí un error node.js del programador. Por ejemplo, si un programa intenta conectarse a un servidor pero recibe un ECONNREFUSEDerror, y no ha registrado un controlador para el 'error'evento del socket , el programa se bloqueará y eso es un error del programador. La falla de la conexión es un error operacional (ya que eso es algo que cualquier programa correcto puede experimentar cuando la red u otros componentes en el sistema han fallado), pero la falla en su manejo es un error del programador.

La distinción entre los errores operativos y los errores del programador es la base para descubrir cómo entregar los errores y cómo manejarlos. Asegúrate de entender esto antes de seguir leyendo.

Manejo del error node.js operacional.

Al igual que el rendimiento y la seguridad, el manejo del error node.js no es algo que se pueda vincular a un programa que ya no tenga manejo de errores. Tampoco puede centralizar todo el manejo de errores en una parte del programa, de la misma manera que no puede centralizar el “rendimiento” en una parte del programa. Cualquier código que hace todo lo que eventualmente podría fallar (abrir un archivo, se conecta a un servidor, que se bifurcan un proceso hijo, etc.) tiene que considerar qué sucede cuando esa operación falla. Eso incluye saber cómo puede fallar (el modo de falla ) y lo que tal falla indicaría. Más sobre esto más adelante, pero el punto clave aquí es que el manejo del error node.js se debe hacer de manera detallada porque el impacto y la respuesta dependen exactamente de lo que falló y por qué.

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 node.js a su interlocutor, lo que propaga el error a su interlocutor, 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 algo más. Pero eso no significa que deba intentar informar todos los errores a una única devolución de llamada de nivel superior, ya que la devolución de llamada en sí misma no puede saber en qué contexto ocurrió el error, qué partes de una operación se completaron con éxito y cuáles realmente fallaron. .

Vamos a hacer esto concreto. Para cualquier error dado, hay algunas cosas que puedes hacer:

  • Tratar con el fracaso directamente. 

    A veces, está claro lo que tienes que hacer para manejar un error. Si recibe un ENOENTerror al intentar abrir un archivo de registro, tal vez esta sea la primera vez que el programa se ejecuta en este sistema y solo necesita crear el archivo de registro primero. Un caso más interesante podría ser cuando se mantiene una conexión persistente a un servidor (por ejemplo, una base de datos), y se produce un error de “bloqueo de socket”. Por lo general, esto significa que el lado remoto o la red se han desintegrado, y con frecuencia es transitorio, por lo que normalmente tratará esto mediante la reconexión. (Esto no es lo mismo que volver a intentarlo, a continuación, ya que no hay necesariamente una operación en marcha cuando se produce este error).

  • Propaga el fallo a tu cliente. 

    Si no sabe cómo lidiar con el error node.js, lo más simple es abortar cualquier operación que esté tratando de hacer, limpiar lo que haya comenzado y devolver un error a su cliente. ( Cómo emitir ese error es otra pregunta, y se explica más adelante). Esto es apropiado cuando espera que lo que sea que haya causado el error no cambie pronto. Por ejemplo, si el usuario le dio un JSON no válido, no ayudará a intentar analizarlo de nuevo.

  • Vuelva a intentar la operación. 

    Para errores de la red y servicios remotos (por ejemplo, un servicio web), a veces es útil reintentar una operación que devuelve un error node.js. Por ejemplo, si un servicio remoto da un 503 (Error de servicio no disponible), es posible que desee volver a intentarlo en unos segundos. Si va a volver a intentarlo, debe documentar claramente que puede volver a intentarlo varias veces, cuántas veces intentará antes de fallar y cuánto tiempo esperará entre los reintentos. Además, no asuma que siempre debe volver a intentar una operación. Si tienes varias capas en la pila (por ejemplo, un cliente te llama, lo llama otro cliente y es manejado por un humano), por lo general es mejor fallar rápido y dejar que el cliente final se ocupe. con reintentos. Si cada capa de la pila piensa que necesita volver a intentarlo en caso de errores, el usuario puede esperar mucho más tiempo de lo debido porque, debido a que cada capa no se dio cuenta de que la capa subyacente también estaba reintentando.

  • Explotar el error node.js. 

    Para los errores que realmente no pueden ocurrir, o que representarían efectivamente los errores del programador si alguna vez lo hicieron (por ejemplo, no se pudo conectar a un socket localhost que se supone que está escuchando en el mismo programa), está bien registrar un mensaje de error y bloquearse. De todos modos, otros errores, como quedarse sin memoria, no pueden manejarse en un lenguaje dinámico como JavaScript, por lo que puede ser totalmente razonable fallar. (Dicho esto, puede obtener ENOMEMoperaciones discretas como child_process.exec, y aquellas que puedemanejar razonablemente, y debe considerar hacerlo.) También puede explotar si no hay nada que pueda hacer razonablemente sobre algo y un administrador necesita arreglarlo. Por ejemplo, si se queda sin descriptores de archivo o no tiene permiso para acceder a su archivo de configuración, no hay nada que pueda hacer al respecto, y un usuario tendrá que iniciar sesión y arreglar las cosas de todos modos.

  • Registre el error y no haga nada más. 

    A veces, no hay nada que puedas hacer al respecto, no hay nada que reintentar o cancelar, y tampoco hay razón para bloquear el programa. Un ejemplo podría ser si está haciendo un seguimiento de un grupo de servicios remotos que usan DNS y uno de esos servicios se cae del DNS. No hay nada que pueda hacer al respecto, excepto registrar un mensaje y continuar con los servicios restantes. Pero al menos debes registrar algo en este caso. (Hay excepciones a todas las reglas. Si esto es algo que puede suceder miles de veces por segundo, y no hay nada que puedas hacer al respecto, probablemente no valga la pena registrarlo cada vez que sucede. Sin embargo, hazlo periódicamente).

manejando el error node.js del programador

No hay nada que puedas hacer para manejar un error node.js de un programador. Por definición, el código que se suponía debía hacer algo estaba roto (por ejemplo, tenía un nombre de variable mal escrito), por lo que no se puede solucionar el problema con más código. Si pudiera, simplemente usaría el código de manejo de errores en lugar del código roto.

Algunas personas abogan por intentar recuperarse de los errores del programador, es decir, permitir que la operación actual falle, pero siguen manejando las solicitudes. Esto no es recomendable. Considere que un error de programador es un caso en el que no pensó cuando escribió el código original. ¿Cómo puede estar seguro de que el problema no afectará a otras solicitudes? Si otras solicitudes comparten un estado común (un servidor, un socket, un grupo de conexiones de base de datos, etc.), es muy posible que las otras solicitudes hagan lo incorrecto.

Un ejemplo típico es un servidor REST (por ejemplo, usando Restify ) donde uno de los manejadores de solicitudes lanza un ReferenceError (por ejemplo, utiliza un nombre de variable mal escrito). Hay muchas formas en que esto puede conducir a errores graves que son extremadamente difíciles de rastrear. Para algunos ejemplos:

  1. Algunos estados compartidos por solicitudes pueden dejarse nullundefinedo de lo contrario no ser válidos, de modo que cuando la próxima solicitud intente usarla, también explote.
  2. Se puede perder una conexión de base de datos (u otra), lo que reduce la cantidad de solicitudes futuras que puede manejar en paralelo. Esto puede llegar a ser tan malo que solo le quedan unas pocas conexiones, y termina manejando solicitudes en serie en lugar de concurrentemente.
  3. Peor aún, una conexión postgres puede quedar dentro de una transacción abierta. Esto hace que postgres se “cuelgue” en versiones antiguas de filas en la tabla porque pueden ser visibles para esa transacción. Esto puede permanecer abierto durante semanas, lo que da como resultado una tabla cuyo tamaño efectivo crece sin límite, lo que provoca que las consultas posteriores disminuyan su orden de magnitud, desde unos pocos milisegundos hasta un minuto. Si bien este problema obviamente es específico de postgres, es un ejemplo de lo mal que puede estar el estado de un programa incluso después de un simple error de programador.
  4. Una conexión puede dejarse en un estado autenticado y usarse para una conexión posterior. Puede terminar ejecutando una solicitud para el usuario incorrecto.
  5. Un zócalo puede dejarse abierto. Normalmente, el nodo usa un tiempo de espera de 2 minutos en los sockets inactivos, pero esto puede ser anulado, lo que resulta en un descriptor de archivo filtrado. Si esto sucede lo suficiente, puede quedarse sin descriptores de archivos y fallar. Incluso si no anula este tiempo de espera, el cliente puede bloquearse durante dos minutos y luego ver un error inesperado de “colgar”. El retraso de dos minutos hace que el problema sea molesto de tratar y de depurar.
  6. Las referencias de memoria se pueden dejar alrededor. Esto produce una fuga, lo que provoca que se quede sin memoria, o (peor) que aumente el tiempo empleado en GC, lo que hace que el rendimiento se estanque de forma horrible. Esto es particularmente difícil de depurar, y sería especialmente difícil asociarlo con los errores del programador que provocaron la fuga.

La mejor manera de recuperarse del error node.js del programador es bloquearse inmediatamente.

Debe ejecutar sus programas utilizando un reinicio que reiniciará automáticamente el programa en caso de un bloqueo. Con un reinicio en marcha, el bloqueo es la forma más rápida de restaurar un servicio confiable ante un error node.js transitorio de programador.

La única desventaja de fallar en el error node.js de programación es que los clientes conectados pueden verse interrumpidos temporalmente, pero recuerde:

  • Por definición, estos errores siempre son errores. No estamos hablando de fallas legítimas del sistema o de la red, sino de errores reales en el programa. Deben ser raras en producción, y la máxima prioridad debe ser depurarlas y arreglarlas.
  • Para todos los casos descritos anteriormente (y muchos más), las solicitudes en vuelo no necesariamente se completarán con éxito de todos modos. Pueden completarse con éxito, pueden fallar el servidor nuevamente, pueden completarse incorrectamente de maneras obvias o pueden completarse erróneamente de maneras muy sutiles que son muy difíciles de depurar.
  • En un sistema distribuido confiable, los clientes deben poder lidiar con las fallas del servidor reconectando y volviendo a intentar las solicitudes. Las fallas de la red y del sistema son una realidad, ya sea que el propio programa Node.js le cause un error node.js pueda o no bloquearse.
  • Si su programa de producción se bloquea tan a menudo que estas desconexiones son un problema, entonces el problema real es que el servidor tiene tantos errores, no que se bloquee en el caso de un error node.js.

Si la desconexión de los clientes es un problema frecuente debido a que un servidor falla con tanta frecuencia, debe concentrarse en el error node.js que causa que el servicio se caiga, y hacer que sean excepcionales, en lugar de intentar evitar fallos en los casos en que el código es obviamente incorrecto. La mejor manera de depurar estos problemas es configurar el Nodo para volcar el núcleo en una excepción no detectada . Tanto en GNU / Linux como en sistemas basados en illumos, puede usar estos archivos centrales para ver no solo el seguimiento de la pila donde se bloqueó el programa, sino también los argumentos de cada una de estas funciones y la mayoría de los otros objetos de JavaScript, incluso aquellos a los que solo se hace referencia cierres Incluso sin los volcados de núcleo configurados, puede usar la información de la pila y los registros para comenzar con el problema.

Finalmente, recuerde que un error node.js de programador en un servidor simplemente se convierte en un error operacional en un cliente. Los clientes tienen que lidiar con los servidores que fallan y los errores de red. Eso no es solo teórico, ambos realmente ocurren en los sistemas de producción.

Patrones para escribir funciones para el error node.js

Hemos hablado acerca de cómo manejar los errores, pero cuando está escribiendo una nueva función, ¿cómo entrega los errores al código que llamó a su función?

Lo más importante que debe hacer es documentar lo que hace su función, incluidos los argumentos que toma (incluidos sus tipos y cualquier otra restricción), lo que devuelve, qué errores pueden ocurrir y qué significan esos errores. Si no sabe qué errores pueden ocurrir o no sabe qué significan, entonces su programa no puede ser correcto, excepto por accidente. Entonces, si está escribiendo una nueva función, debe informar a las personas que llaman qué errores pueden ocurrir y qué significan.

¿Tiro, devolución de llamada, rechazo o EventEmitter?

Hay tres patrones básicos para que una función entregue errores.

  • throwenvía un error de forma síncrona, es decir, en el mismo contexto donde se llamó a la función. Si la persona que llama (o la persona que llama, …) usó trycatch, entonces pueden detectar el error. Si ninguna de las personas que llamaron lo hizo, el programa generalmente falla. (El error también puede ser manejado por dominios o por el evento “uncaughtException” en todo el proceso, que se analiza a continuación).
  • Las devoluciones de llamada son la forma más básica de enviar un error de forma asíncrona. El usuario le pasa una función (la devolución de llamada), y la invoca más tarde cuando finaliza la operación asíncrona. El patrón habitual es que la devolución de llamada se invoca como callback(err, result), donde solo uno de errresultno es nulo, dependiendo de si la operación se realizó correctamente o no.
  • Los rechazos de promesa son una forma común de enviar un error de forma asíncrona. Este método está creciendo en popularidad desde el lanzamiento de Node.js versión 8 que incluye soporte para asyncawait. Esto permite escribir código asíncrono para que parezca un código síncrono y detectar errores usando trycatch.
  • Para casos más complicados, en lugar de usar una devolución de llamada, la función en sí misma puede devolver un EventEmitterobjeto, y se espera que la persona que llama escuche los erroreventos en el emisor. Esto es útil en dos casos particulares:
    • Cuando estás haciendo una operación complicada que puede producir múltiples errores o resultados múltiples. Por ejemplo, piense en una solicitud que obtiene filas de una base de datos y luego las vuelve a transmitir a medida que llegan, en lugar de esperar a que todas lleguen primero. En este caso, en lugar de recibir una devolución de llamada, su función devolverá un EventEmitter y emitirá roweventos para cada resultado, un endevento cuando se hayan informado todos los resultados y un errorevento si se produce algún error.
    • Para los objetos que representan máquinas de estados complejas, donde pueden ocurrir muchas cosas asíncronas diferentes. Por ejemplo, un socket es un emisor de eventos que puede emitir “connect”, “end”, “timeout”, “dren” y “close”. Es natural cometer “error” simplemente otro tipo de evento que el socket puede emitir. Al utilizar este enfoque, es importante tener claro cuándo se emite “error”, si se pueden emitir otros eventos, qué otros eventos se pueden ver al mismo tiempo (por ejemplo, “cerrar”), en qué orden ocurren, y si el zócalo está cerrado al final.

En su mayor parte, agruparemos las devoluciones de llamada y los emisores de eventos en el mismo grupo de “entrega de error asíncrono”. Si desea enviar un error de forma asíncrona, generalmente desea utilizar uno u otro de estos (devolución de llamada o emisor de eventos), pero no ambos.

Entonces, ¿cuándo se usa throwy cuándo se usan las devoluciones de llamada o los emisores de eventos? Depende de dos cosas:

  • ¿Es el error node.js un error operacional o un error del programador?
  • ¿Es la función en sí misma síncrona o asíncrona?

Con mucho, el caso más común es el error node.js y este es un error  operacional en una función asíncrona. Para la mayoría de estos, querrá que su función tome una devolución de llamada como argumento, y simplemente pasará el error a la devolución de llamada. Esto funciona muy bien, y es ampliamente utilizado. Consulte el fsmódulo Nodo para ver ejemplos. Si tiene un caso más complicado como los descritos anteriormente, es posible que desee utilizar un emisor de eventos en su lugar, pero aún así entregará el error de forma asíncrona.

El siguiente caso más común es un error operacional en una función síncrona JSON.parse. Para estas funciones, si encuentra un error operacional (como una entrada de usuario no válida), debe entregar el error de forma síncrona. Puedes lanzarlo (mucho más común) o devolverlo.

Para una función dada, si cualquier error operacional puede entregarse de forma asíncrona, entonces todos los errores operacionales deben entregarse de manera asíncrona. Puede haber casos en los que sepa inmediatamente que la solicitud fallará, pero no debido a un error node.js del programador. Tal vez la función almacena en caché los resultados de las solicitudes recientes y hay una entrada de caché con un error node.js que devolverá a la persona que llama. Aunque sepa de inmediato que la solicitud fallará, debe entregar ese error de forma asíncrona.

La regla general es que una función puede entregar errores operacionales de forma sincrónica (por ejemplo, mediante throwing) o de manera asíncrona (pasándolos a una devolución de llamada o emitiendo erroren una EventEmitter), pero no debería hacer ambas cosas . De esta manera, un usuario puede manejar los errores ya sea manejándolos en la devolución de llamada o usando trycatch, pero nunca necesitan hacer ambas cosas. Cuál de ellos usan depende de cómo la función entrega sus errores, y eso debe especificarse con su documentación.

Hemos dejado de lado los errores del programador. Recordemos que estos son siempre errores. Por lo general, también se pueden identificar inmediatamente al verificar los tipos (y otras restricciones) en los argumentos al inicio de la función. Un caso degenerado es cuando alguien llama a una función asíncrona pero no pasa una devolución de llamada. Debería lanzar estos errores inmediatamente, ya que el programa está dañado y la mejor posibilidad de depuración implica obtener al menos un seguimiento de pila y, idealmente, un archivo central en el punto del error. Para hacer esto, recomendamos validar los tipos de todos los argumentos al inicio de la función.

Dado que los errores del programador nunca deben manejarse, esta recomendación no cambia nuestra conclusión anterior de que una persona que llama puede usar trycatcho una devolución de llamada (o emisor de eventos) para manejar los errores, pero nunca necesita usar ambos. Para más información, vea “(No) manejar errores de programador” más arriba.

Aquí hay un resumen de estas recomendaciones con algunas funciones de ejemplo en las bibliotecas principales de Node, en orden aproximado de la frecuencia con la que surge cada tipo de problema:

Ejemplo de funciónTipo de funcError de ejemploTipo de errorComo entregarUsadores de llamadas
fs.statasincrónicoarchivo no encontradoOperacionalllamar de vueltamanejar el error de devolución de llamada
JSON.parsesincrónicomala entrada del usuarioOperacionalthrowtry/catch
fs.statasincróniconull para nombre de archivoprogramadorthrowninguno

Los errores operativos en una función asíncrona (fila 1) son, con mucho, el caso más común. El uso de funciones síncronas que informan errores operativos (fila 2) es muy raro en Node.js, excepto en la validación de entrada del usuario. Sin embargo, con el lanzamiento de la versión 8 de Node.js, las personas están comenzando a prometer estas funciones asíncronas y están utilizando esperar dentro de un try / catch. Los errores del programador (fila 3) nunca deben suceder, excepto en el desarrollo.

¿Mala entrada: error node.js del programador o error operacional?

¿Cómo sabes qué es un error node.js de programador frente a un error operacional? En pocas palabras: depende de usted definir y documentar qué tipos permitirá su función y cómo intentará interpretarlos. Si obtienes algo distinto de lo que has documentado para aceptar, es un error node.js de programador. Si la entrada es algo que usted ha documentado que debe aceptar pero no puede procesar ahora, es un error operacional.

Debe usar su criterio para decidir qué tan estricto quiere ser, pero podemos hacer algunas sugerencias. Para concretar, imagine una función llamada “conectar” que toma una dirección IP y una devolución de llamada e invoca la devolución de llamada de forma asíncrona después de tener éxito o fallar. Supongamos que el usuario pasa algo que obviamente no es una dirección IP válida, como 'bob'. En este caso, tienes algunas opciones:

  • Documente que la función solo acepta cadenas que representan direcciones IPv4 válidas y arroje una excepción inmediatamente si el usuario la aprueba 'bob'. Esto es muy recomendable.
  • Documento que la función acepta cualquier cadena. Si el usuario pasa 'bob', emita un error asíncrono que indica que no pudo conectarse a la dirección IP 'bob'.

Ambos de estos son consistentes con las pautas sobre errores operacionales y errores del programador. Realmente está decidiendo si considerar tal entrada como un error de programador o un error operacional. En general, las funciones de validación de entrada del usuario son muy flojas.Date.parse, por ejemplo, acepta una variedad de entradas, ese es básicamente el punto. Pero para la mayoría de las otras funciones, recomendamos enfáticamente que sea más estricto en lugar de más flexible. Cuanto más intente su función adivinar qué quiso decir la persona que llama (usando coerciones implícitas, ya sea como parte de JavaScript o haciéndolo explícitamente en su función), es más probable que adivine mal. En lugar de ahorrar a los desarrolladores el esfuerzo necesario para ser más explícitos, es posible que haga algo que desperdicie horas de tiempo para que el desarrollador realice la depuración. Además, siempre puedes hacer que la función sea menos estricta en futuras versiones si decides que es una buena idea, pero si descubres que tu intento de adivinar qué significaba la gente conduce a errores realmente desagradables, no puedes arreglarlo sin romper la compatibilidad.

Entonces, si un valor no puede ser válido (por ejemplo, undefinedpara una cadena requerida o una cadena que se supone que es una dirección IP pero obviamente no lo es), debe documentar que no está permitido y arrojar un error inmediatamente si ve eso. Mientras lo documente, entonces estos son errores del programador, no errores operacionales. Al lanzar de inmediato, minimiza el daño causado por el error y conserva la información que el desarrollador querría depurar el problema (por ejemplo, la pila de llamadas, y si está utilizando volcados de núcleo, los argumentos y toda la memoria también).

¿Qué pasa con los dominios y process.on('uncaughtException')?

Los errores operativos siempre se pueden manejar a través de un mecanismo explícito: detectar una excepción, procesar el error en una devolución de llamada, manejar un evento de “error” en un EventEmitter, etc. Los dominios y el 'uncaughtException'evento de todo el proceso son principalmente útiles para intentar controlar o recuperar errores de programador no anticipados. Por todas las razones descritas anteriormente, esto es muy desaconsejable.

Recomendaciones específicas para escribir nuevas funciones.

Hemos hablado de muchos principios rectores, así que ahora vamos a ser específicos.

1. Sea claro acerca de lo que hace su función.

Esto es lo más importante que hay que hacer. La documentación para cada función de interfaz debe ser muy clara sobre:

  • que argumentos espera
  • Los tipos de cada uno de esos argumentos.
  • cualquier restricción adicional en esos argumentos (por ejemplo, debe ser una dirección IP válida)

Si alguno de estos está mal o falta, es un error de programador y debe lanzarlo inmediatamente.

También querrás documentar:

  • Qué errores operacionales deben esperar las personas que llaman (incluyendo sus names)
  • cómo manejar los errores operacionales (por ejemplo, se lanzarán, pasarán a la devolución de llamada, se emitirán en un emisor de eventos, etc.)
  • el valor de retorno

2. Utilice Errorobjetos (o subclases) para todos los errores e implemente el Errorcontrato.

Todos sus errores deben usar la clase Error o una subclase de la misma. Debe proporcionar namemessagepropiedades, y stackdebe trabajar también (y ser preciso).

3. Use la namepropiedad del error para distinguir los errores programáticamente.

Cuando necesite averiguar qué tipo de error es este, use la namepropiedad. Los nombres de JavaScript incorporados que puede reutilizar incluyen “RangeError” (un argumento está fuera de su rango válido) y “TypeError” (un argumento tiene el tipo incorrecto). Para errores HTTP, es común usar el texto de estado dado por RFC para nombrar el error, como “BadRequestError” o “ServiceUnavailableError”.

No sientas la necesidad de crear nuevos nombres para todo. No necesita InvalidHostnameError, InvalidIpAddressError, InvalidDnsServerError, etc., cuando solo puede tener un solo InvalidArgumentError y aumentarlo con propiedades que digan lo que está mal (vea más abajo).

4. Aumentar el Errorobjeto con propiedades que explican detalles.

Por ejemplo, si un argumento no era válido, establezca propertyNameel nombre de la propiedad que no era válida y propertyValueel valor que se pasó. Si no pudo conectarse a un servidor, use remoteIppara decir a qué IP intentó conectarse. Si recibió un error del sistema, incluya la syscallpropiedad para indicar qué syscall falló y la errnopropiedad para decir qué sistema errno recibió. Vea el apéndice para ejemplos de nombres de propiedades para usar.

Por lo menos, usted quiere:

  • name: se usa para distinguir de manera programática entre tipos amplios de errores (p. ej., argumento ilegal contra conexión fallida)
  • message: un mensaje de error legible por humanos. Esto debería ser lo suficientemente completo para que cualquiera que lo lea lo entienda. Si está pasando un error desde un nivel inferior de la pila, debe agregar algo al mensaje que explique lo que estaba haciendo. Vea el siguiente artículo para más información sobre envolver errores.
  • stack: en general, no te metas con esto. Ni siquiera aumentarlo. V8 solo lo calcula si alguien realmente lee la propiedad, lo que mejora dramáticamente el rendimiento para errores manejables. Si lees la propiedad solo para aumentarla, terminarás pagando el costo incluso si la persona que llama no necesita la pila.

También debe incluir suficiente información en el mensaje de error para que la persona que llama elabore su propio mensaje de error sin tener que analizar el suyo. Es posible que deseen localizar el mensaje de error, agregar un gran número de errores o mostrar el mensaje de error de manera diferente (por ejemplo, en una tabla en un sitio web, o resaltando un campo de formulario de entrada de usuario incorrecto).

5. Si le pasa un error de nivel inferior a su interlocutor, considere envolverlo.

A menudo, encontrará que su función asíncrona funcAllama a otra función asíncrona funcBy que, si funcBemite un error, querrá funcAemitir el mismo error. (Tenga en cuenta que la segunda parte no siempre sigue a la primera. A veces funcA, en cambio, lo volveremos a intentar. O a veces funcAignorará el error porque puede significar que no hay nada que hacer. Pero solo estamos considerando el caso simple en el que funcAquiere para devolver directamente funcBel error de aquí.

En este caso, considere envolver el error en lugar de devolverlo directamente. Al envolver, nos referimos a la propagación de un nuevo error que incluye toda la información del nivel inferior, más un contexto útil adicional basado en el nivel actual. El módulo verror proporciona una manera fácil de hacer esto.

Por ejemplo, suponga que tiene una función llamada fetchConfig, que recupera la configuración de un servidor desde una base de datos remota. Tal vez usted llama a esta función cuando se inicia su servidor. Todo el camino en el inicio se ve así:

  1. Cargar configuracion
    1. Conéctese al servidor de la base de datos. Esto a su vez:
      1. Resolver el nombre de host DNS del servidor de base de datos
      2. Haga una conexión TCP al servidor de base de datos.
      3. Autenticación en el servidor de la base de datos.
    2. Hacer la solicitud de DB
    3. Decodificar la respuesta.
    4. Cargar la configuracion
  2. Empezar a manejar solicitudes

Supongamos que en el tiempo de ejecución hay un problema al conectarse al servidor de la base de datos. Si el paso de conexión en 1.1.2 falla porque no hay una ruta hacia el host, y cada nivel propaga el error a la persona que llama (como debería), pero no envuelve el error primero, entonces puede aparecer un mensaje de error como este :

myserver: Error: connect ECONNREFUSED

Esto obviamente no es muy útil.

Por otro lado, si cada nivel envuelve el error devuelto desde el nivel inferior, puede obtener un mensaje mucho más informativo:

myserver: failed to start up: failed to load configuration: failed to connect to
database server: failed to connect to 127.0.0.1 port 1234: connect ECONNREFUSED

Es posible que desee omitir el ajuste en algunos niveles y obtener un mensaje menos pedante:

myserver: failed to load configuration: connection refused from database at
127.0.0.1 port 1234.

Por otro lado, es mejor equivocarse al incluir más información en lugar de menos.

Si decide envolver un error, hay algunas cosas que debe considerar:

  • Deje el error original intacto y sin cambios, y asegúrese de que el error subyacente todavía esté disponible para la persona que llama en caso de que desee obtener información de él directamente.
  • Use el mismo “nombre” para su error, o bien elija explícitamente uno que tenga más sentido. Por ejemplo, el nivel inferior podría ser un simple Error desde el nodo, pero el error del paso 1 podría ser un error de inicialización. (Pero no se sienta obligado a crear nuevos nombres para errores si pueden distinguirse programáticamente mirando las otras propiedades).
  • Preserve todas las propiedades del error original. Aumente la messagepropiedad según corresponda (pero no la cambie en el error original). Shallow-copia todas las otras propiedades como syscallerrnoy similares. Usted es el mejor de copiar todas las propiedades excepto por namemessagestack, en lugar de codificar una lista de propiedades para copiar de manera explícita. No hagas nada con eso stack, ya que incluso leerlo puede ser relativamente caro. Si la persona que llama quiere producir una pila combinada, debe iterar las causas e imprimir la pila de cada una.

En Joyent, usamos el módulo verror para envolver los errores, ya que es sintácticamente conciso. A partir de este escrito, todavía no hace todo esto, pero se extenderá a hacerlo.

Un ejemplo del error node.js

Considere una función que se conecta de forma asíncrona a un puerto TCP en una dirección IPv4. Aquí hay un ejemplo de cómo podemos documentarlo:

/*
 * Make a TCP connection to the given IPv4 address.  Arguments:
 *
 *    ip4addr        a string representing a valid IPv4 address
 *
 *    tcpPort        a positive integer representing a valid TCP port
 *
 *    timeout        a positive integer denoting the number of milliseconds
 *                   to wait for a response from the remote server before
 *                   considering the connection to have failed.
 *
 *    callback       invoked when the connection succeeds or fails.  Upon
 *                   success, callback is invoked as callback(null, socket),
 *                   where `socket` is a Node net.Socket object.  Upon failure,
 *                   callback is invoked as callback(err) instead.
 *
 * This function may fail for several reasons:
 *
 *    SystemError    For "connection refused" and "host unreachable" and other
 *                   errors returned by the connect(2) system call.  For these
 *                   errors, err.errno will be set to the actual errno symbolic
 *                   name.
 *
 *    TimeoutError   Emitted if "timeout" milliseconds elapse without
 *                   successfully completing the connection.
 *
 * All errors will have the conventional "remoteIp" and "remotePort" properties.
 * After any error, any socket that was created will be closed.
 */
function connect(ip4addr, tcpPort, timeout, callback) {
  assert.equal(typeof (ip4addr), 'string',
      "argument 'ip4addr' must be a string");
  assert.ok(net.isIPv4(ip4addr),
      "argument 'ip4addr' must be a valid IPv4 address");
  assert.equal(typeof (tcpPort), 'number',
      "argument 'tcpPort' must be a number");
  assert.ok(!isNaN(tcpPort) && tcpPort > 0 && tcpPort < 65536,
      "argument 'tcpPort' must be a positive integer between 1 and 65535");
  assert.equal(typeof (timeout), 'number',
      "argument 'timeout' must be a number");
  assert.ok(!isNaN(timeout) && timeout > 0,
      "argument 'timeout' must be a positive integer");
  assert.equal(typeof (callback), 'function');
  /* do work */
}

Este ejemplo es conceptualmente simple, pero demuestra algunas de las sugerencias de las que hablamos:

  • Los argumentos, sus tipos y otras restricciones en sus valores están claramente documentados.
  • La función es estricta en los argumentos que acepta y arroja errores (errores de programación) cuando se obtiene una entrada no válida.
  • Se documenta el conjunto de posibles errores operacionales. Los diferentes valores de “nombre” se utilizan para distinguir errores lógicamente diferentes, y “errno” se utiliza para obtener información detallada sobre los errores del sistema.
  • La forma en que se entregan los errores se documenta ( callbackse invoca en caso de error).
  • Los errores devueltos tienen los campos “remoteIp” y “remotePort” para que un usuario pueda definir un mensaje de error personalizado (por ejemplo, cuando el número de puerto está implícito, como lo sería con un cliente HTTP).
  • Aunque debería ser obvio, el estado después de una conexión fallida está claramente documentado: cualquier socket que se haya abierto ya se habrá cerrado.

Esto puede parecer más trabajo de lo que la gente suele poner por escrito en lo que debería ser una función bien entendida, pero la mayoría de las funciones no son tan universalmente entendidas. Todos los consejos deben ser ajustados y debe usar su criterio si algo es realmente simple, pero recuerde: diez minutos documentando las expectativas ahora pueden ahorrarle horas a usted u otra persona más adelante.

Resumen del error node.js

  • Aprenda a distinguir entre errores operativos , que son anticipables, errores inevitables, incluso en programas correctos (por ejemplo, fallar en conectarse a un servidor), y errores de programador , que son errores en el programa.
  • Los errores operacionales pueden y deben ser manejados. Los errores del programador no pueden manejarse o recuperarse de manera confiable (ni deberían serlo), y tratar de hacerlo hace que sean más difíciles de depurar.
  • Una función dada debe entregar errores operacionales ya sea de forma síncrona (con throw) o asíncronamente (con una devolución de llamada o emisor de eventos), pero no ambas. Un usuario debería poder usar trycatcho manejar los errores en la devolución de llamada, pero nunca debería necesitar ambos. En general, usar throwy esperar que una persona que llama use trycatches bastante raro, ya que en Node.js no es común que las funciones síncronas tengan errores operativos. (La principal excepción son las funciones de validación de entrada del usuario, como JSON.parse.)
  • Al escribir una nueva función, documente claramente los argumentos que su función espera, sus tipos, cualquier otra restricción (por ejemplo, “debe ser una dirección IP válida”), los errores operacionales que pueden ocurrir legítimamente (por ejemplo, falla para resolver un nombre de host, falla al conectarse a un servidor, cualquier error del lado del servidor, y cómo esos errores se entregan al llamante (de forma síncrona, usando throwo asíncronamente, usando una devolución de llamada o un emisor de eventos).
  • Los argumentos que faltan o no son válidos son errores del programador, y siempre debe hacerlo throwcuando eso sucede. Puede haber un área gris alrededor de los parámetros que el autor decide que son aceptables, pero si pasa una función diferente de lo que está documentado para aceptar, eso siempre es un error del programador.
  • Al entregar errores, use la Errorclase estándar y sus propiedades estándar. Agregue toda la información adicional que pueda ser útil en propiedades separadas. Cuando sea posible, utilice nombres de propiedades convencionales (ver más abajo).

Apéndice: Propiedades convencionales para objetos de error node.js .

Se recomienda encarecidamente que utilice estos nombres para mantener la coherencia con los Errores entregados por el núcleo de Node y los complementos de Node. La mayoría de estos no se aplicarán a ningún error dado, pero en caso de duda, debe incluir cualquier información que parezca útil, tanto por programación como por un mensaje de error personalizado.

Nombre de la propiedadUso previsto
localHostnameel nombre de host DNS local (por ejemplo, que está aceptando conexiones en)
localIpla dirección IP local (por ejemplo, que está aceptando conexiones en)
puerto localel puerto TCP local (por ejemplo, que está aceptando conexiones en)
nombre de usuario remotoel nombre de host DNS de algún otro servicio (por ejemplo, al que intentó conectarse)
IP remotala dirección IP de algún otro servicio (p. ej., al que intentó conectarse)
Puerto remotoel puerto de algún otro servicio (p. ej., al que intentó conectarse)
caminoel nombre de un archivo, directorio o Unix Domain Socket (p. ej., que intentó abrir)
srcpathel nombre de una ruta utilizada como fuente (por ejemplo, para cambiar el nombre o copiar)
dstpathel nombre de una ruta utilizada como destino (por ejemplo, para cambiar el nombre o copiar)
nombre de hostun nombre de host DNS (por ejemplo, que intentó resolver)
ipuna dirección IP (p. ej., que intentó revertir-resolver)
nombre de la propiedadun nombre de propiedad de objeto o un nombre de argumento (por ejemplo, para un error de validación)
valor de propiedadun valor de propiedad de objeto (por ejemplo, para un error de validación)
syscallel nombre de una llamada al sistema que falló
errnoel valor simbólico de errno(por ejemplo, "ENOENT").
No , no utilizar esto para los errores que en realidad no ajustan el valor de Cerrno.
Utilice “nombre” para distinguir entre los tipos de errores.

Notas al pie

const func = () => {
  return new Promise((resolve, reject) => {
    setImmediate(() => {
      throw new Error('foo');
    });
  });
};
const main = async () => {
  try {
    await func();
  } catch (ex) {
    console.log('will not execute');
  }
};
main();

  1. La gente a veces escribe un código como este cuando quiere manejar el error asíncrono invocando la función callback y pasando el error como un argumento. Pero cometen el error de pensar que si lo lanzan desde su propia devolución de llamada (la función pasó a doSomeAsynchronousOperation ), entonces se puede capturar en el catch < / código> bloque. No es así como try / catch funciona con funciones asíncronas. Recuerde que todo el punto de una función asincrónica es que se invoca algún tiempo más tarde , después de que myApiFunc vuelva. Eso significa que se ha salido del bloque try . La devolución de llamada es invocada directamente por Node, sin  intente bloquear a su alrededor. Por lo tanto, si usa este anti-patrón, terminará fallando el programa cuando lance el error. Evento en el caso de una función async explícita que utiliza una await en el bloque try , no se detectará un error generado de forma asíncrona. A continuación se muestra un ejemplo de un error que no se detectará. & # 160; & # 8617; < / p> un error lanzado de forma asíncrona no será atrapado. A continuación se muestra un ejemplo de un error que no se detectará. & # 160; & # 8617; < / p> un error lanzado de forma asíncrona no será atrapado. A continuación se muestra un ejemplo de un error que no se detectará. & # 160; & # 8617; < / p>
  2. En JavaScript, técnicamente puedes lanzar cosas que no son Errores, pero esto debería evitarse. El resultado no incluye el potencial para obtener una pila de llamadas, ni un & quot; nombre & quot; propiedad para inspección programática, ni propiedades útiles que describan lo que salió mal. & # 160; & # 8617; < / p>
  3. Los conceptos de un error operacional y un error de programador son anteriores al Node.js. En Java, esto rastrea el uso de excepciones marcadas y no controladas, aunque los errores operacionales que se sabe que son imposibles de manejar, como OutOfMemoryError , se agrupan con excepciones no verificadas. En C, es análogo al manejo normal de errores en comparación con el uso de una aserción, y el artículo de Wikipedia sobre aserciones tiene un similar explicación de cuándo usar aserciones frente al manejo normal de errores . & # 160; & # 8617; </ a >
  4. Si eso suena extrañamente específico, es porque hemos visto esto en producción. Y fue terrible. & # 160; & # 8617;

Deseas conocer nuestros servicios visita: https://www.wolfpress.co/

Quieres conocer más de nuestra información visita: https://www.wolfpress.co/blog/

🌟 ¡Descubre, Aprende y Disfruta! 🌟 🔥 Sumérgete en un mundo de contenido fascinante y déjate cautivar por artículos emocionantes que abarcan desde tecnología hasta cultura. ¡La aventura te espera! 🔥
Bienvenido a la guía definitiva sobre los mejores códigos de música de Roblox para el mes de febrero de 2024 ...
Si estás buscando un contador con redirección implementado en HTML, probablemente estés buscando un script que pueda llevar un registro ...
El código proporcionado es una página web simple que presenta dos botones ("Sí" y "No") y un título ("¿Me amas?") ...
Un comentario en «Error node.js manejo y solución»

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Recommended
En varias oportunidades hemos hablado sobre la importancia del error…
Cresta Posts Box by CP