Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Web Development
Code

Gestionando la naturaleza asíncrona de Node.js

by
Difficulty:IntermediateLength:LongLanguages:

Spanish (Español) translation by CYC (you can also view the original English article)

Node.js te permite crear aplicaciones rápida y fácilmente. Pero debido a su naturaleza asíncrona, puede ser difícil escribir código legible y manejable. En este artículo te mostraré algunos consejos sobre cómo lograrlo.


Callback Hell o la pirámide de Doom

Node.js está construido de una manera que te obliga a usar funciones asíncronas. Eso significa devoluciones de llamada, devoluciones de llamada y aún más devoluciones de llamada. Probablemente has visto o incluso escrito piezas de código como esta:

Esto es en realidad un fragmento de una de mis primeras aplicaciones Node.js. Si has hecho algo más avanzado en Node.js, es probable que lo entiendas todo, pero el problema aquí es que el código se está moviendo a la derecha cada vez que usas alguna función asíncrona. Se vuelve más difícil de leer y más difícil de depurar. Por suerte, hay algunas soluciones para este lío, por lo que puede elegir la correcta para su proyecto.


Solución 1: Denominación de retorno de llamada y modularización

El enfoque más simple sería nombrar cada devolución de llamada (que le ayudará a depurar el código) y dividir todo su código en módulos. El ejemplo de inicio de sesión anterior se puede convertir en un módulo en unos pocos pasos simples.

La estructura

Vamos a empezar con una estructura de módulo simple. Para evitar la situación anterior, cuando acaba de dividir el desastre en pequeños, tengamos que sea una clase:

La clase se construye con dos parámetros: nombre de usuario y contraseña . Mirando el código de muestra, necesitamos tres funciones: una para verificar si el nombre de usuario es correcto ( _checkUsername ), otra para verificar la contraseña ( _checkPassword ) y una más para devolver los datos relacionados con el usuario ( _getData ) y notificar a la aplicación que inicio de sesión fue exitoso También hay un ayudante _checkForErrors , que manejará todos los errores. Finalmente, hay una función de ejecución , que iniciará el procedimiento de inicio de sesión (y es la única función pública en la clase). Finalmente, heredamos de EventEmitter para simplificar el uso de esta clase.

El ayudante

La función _checkForErrors verificará si ocurrió algún error o si la consulta SQL no devuelve filas, y emitirá el error apropiado (con la razón que se suministró):

También devuelve verdadero o falso , dependiendo de si se produjo un error o no.

Realizando el Login

La función de ejecución tendrá que realizar una sola operación: realizar la primera consulta SQL (para verificar si existe el nombre de usuario) y asignar la devolución de llamada adecuada:

Supongo que tiene su conexión SQL accesible globalmente en la variable sql (solo para simplificar, discutir si esta es una buena práctica está más allá del alcance de este artículo). Y eso es todo para esta función.

Comprobando el nombre de usuario

El siguiente paso es verificar si el nombre de usuario es correcto y, si es así, activar la segunda consulta: para verificar la contraseña:

Más o menos el mismo código que en la muestra desordenada, con la excepción del manejo de errores.

Comprobando la contraseña

Esta función es casi exactamente igual a la anterior, la única diferencia es la consulta llamada:

Obtención de los datos relacionados con el usuario

La última función en esta clase obtendrá los datos relacionados con el usuario (el paso opcional) y disparará un evento exitoso con él:

Toques finales y uso

Lo último que hay que hacer es exportar la clase. Agregue esta línea después de todo el código:

Esto hará que la clase de inicio de sesión sea lo único que el módulo exportará. Se puede usar más tarde de esta manera (suponiendo que haya nombrado el archivo de módulo login.js y esté en el mismo directorio que el script principal):

Aquí hay algunas líneas más de código, pero la legibilidad del código se ha incrementado notablemente. Además, esta solución no utiliza ninguna biblioteca externa, lo que la hace perfecta si alguien nuevo viene a tu proyecto.

Ese fue el primer acercamiento, pasemos al segundo.


Solución 2: Promesas

Usar promesas es otra forma de resolver este problema. Una promesa (como puede leer en el enlace provisto) "representa el valor final devuelto por la finalización única de una operación". En la práctica, significa que puede encadenar las llamadas para aplanar la pirámide y hacer que el código sea más fácil de leer.

Usaremos el módulo Q , disponible en el repositorio NPM.

Q en la cáscara de nuez

Antes de empezar, permítanme presentarles la Q. Para las clases estáticas (módulos), utilizaremos principalmente la función Q.nfcall . Nos ayuda en la conversión de cada función siguiendo los Node.js's patrón de devolución de llamada (donde los parámetros de la devolución de llamada son el error y el resultado) a una promesa. Se usa así:

Es muy parecido a Object.prototype.call . También puede usar el Q.nfapply que se parece a Object.prototype.apply :

Además, cuando creamos la promesa, agregamos cada paso con el método then (stepCallback) , detectamos los errores con catch (errorCallback) y terminamos con done () .

En este caso, dado que el objeto sql es una instancia, no una clase estática, tenemos que usar Q.ninvoke o Q.npost , que son similares a los anteriores. La diferencia es que pasamos el nombre de los métodos como una cadena en el primer argumento, y la instancia de la clase con la que queremos trabajar como segundo, para evitar que el método se desvincule de la instancia.

Preparando la promesa

Lo primero que debes hacer es ejecutar el primer paso, usando Q.nfcall o Q.nfapply (usa el que más te guste, no hay diferencia debajo):

Observe la falta de un punto y coma al final de la línea: las llamadas a la función se encadenarán para que no puedan estar allí. Acabamos de llamar a sql.query como en el ejemplo desordenado, pero omitimos el parámetro de devolución de llamada, que está a cargo de la promesa.

Comprobando el nombre de usuario

Ahora podemos crear la devolución de llamada para la consulta SQL, será casi idéntica a la del ejemplo "pirámide de la fatalidad". Agregue esto después de la llamada Q.ninvoke :

Como puede ver, estamos adjuntando la devolución de llamada (el siguiente paso) utilizando el método then . Además, en la devolución de llamada omitimos el parámetro de error , ya que detectaremos todos los errores más adelante. Estamos comprobando manualmente, si la consulta devolvió algo, y si es así, estamos devolviendo la siguiente promesa de ejecución (de nuevo, sin punto y coma debido al encadenamiento).

Comprobando la contraseña

Al igual que con el ejemplo de modularización, verificar la contraseña es casi idéntico a verificar el nombre de usuario. Esto debe ir a la derecha después de la última continuación, llame al:

Obtención de los datos relacionados con el usuario

El último paso será en el que colocaremos los datos de los usuarios en la sesión. Una vez más, la devolución de llamada no es muy diferente del ejemplo desordenado:

Comprobación de errores

Cuando se usan promesas y la biblioteca Q, todos los errores son manejados por el conjunto de devolución de llamada usando el método catch . Aquí, solo estamos enviando el HTTP 500 sin importar el error, como en los ejemplos anteriores:

Después de eso, debemos llamar al método " hecho " para asegurarnos de que, si un error no se maneja antes del final, se volverá a generar y se informará "(del archivo README de la biblioteca). Ahora nuestro código bellamente aplanado debería tener este aspecto (y comportarse como el desordenado):

El código es mucho más limpio e implicó menos reescritura que el enfoque de modularización.


Solución 3: Step Library

Esta solución es similar a la anterior, pero es más simple. Q es un poco pesado, porque implementa toda la idea de las promesas. La biblioteca Step está allí solo con el propósito de aplanar el infierno de devolución de llamada. También es un poco más fácil de usar, ya que solo llama a la única función que se exporta desde el módulo, pasa todas sus devoluciones de llamada como parámetros y utiliza esto en lugar de cada devolución de llamada. Entonces, el ejemplo desordenado se puede convertir en esto, usando el módulo Step:

El inconveniente aquí es que no hay un controlador de errores común. Aunque cualquier excepción lanzada en una devolución de llamada se pasa al siguiente como primer parámetro (por lo tanto, el script no se desactiva debido a la excepción no detectada), tener un controlador para todos los errores es conveniente la mayor parte del tiempo.


¿Cuál elegir?

Esa es una elección bastante personal, pero para ayudarte a elegir la correcta, aquí hay una lista de ventajas y desventajas de cada enfoque:

Modularización:

Pros:

  • No hay bibliotecas externas
  • Ayuda a hacer el código más reutilizable.

Contras:

  • Más código
  • Un montón de reescritura si estás convirtiendo un proyecto existente

Promesas (Q):

Pros:

  • Menos código
  • Solo un poco de reescritura si se aplica a un proyecto existente

Contras:

  • Tienes que usar una biblioteca externa
  • Requiere un poco de aprendizaje.

Biblioteca de pasos:

Pros:

  • Fácil de usar, no requiere aprendizaje
  • Bastante copiar y pegar si convertir un proyecto existente

Contras:

  • Ningún controlador de error común
  • Un poco más difícil de sangrar esa función paso correctamente

Conclusión

Como puede ver, la naturaleza asíncrona de Node.js se puede gestionar y se puede evitar el infierno de devolución de llamada. Personalmente estoy usando el enfoque de modularización, porque me gusta que mi código esté bien estructurado. Espero que estos consejos te ayuden a escribir tu código de manera más legible y a depurar tus scripts más fácilmente.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.