Promesas: Organiza tu código Javascript/Coffeescript

0
14417

Promesas: Organiza tu código Javascript/Coffeescript

Índice

Introducción

En Javascript es habitual escribir código que se ejecutará de forma asíncrona, sobre todo si nuestra aplicación hace uso de librerías de entrada/salida. Tanto si escribimos una aplicación web con Javascript corriendo en
un navegador con las llamadas Ajax por ejemplo o una aplicación Javascript corriendo en servidor sobre una plataforma tipo Node.js, las funciones asíncronas suelen obligarnos a tener una anidación de funciones
para tratar las diferentes callbacks que hacen poco legible nuestro código y complica el tratamiento de errores.

Las Promesas son un paradigma de programación en Javascript que hace que nuestro código sea más legible, evitando las callbacks anidadas, y facilitando el tratamiento de errores creando un código estilo
try… catch… finally.

La definición que la propuesta de CommonJS Promises/A hace, es que una promesa es un valor que se espera en un tiempo futuro no definido, después de ejecutar una operación. Esta definición encaja en las funciones
asíncronas que manejamos habitualmente cuando desarrollamos en Javascript.

Una promesa puede estar en tres estados, no cumplida, cumplida, fallida. Una promesa no cumplida sólo puede transicionar a cumplida o fallida, es decir, mientras ejecutamos nuestro código asíncrono y éste
no finalice, nuestra promesa estará incumplida.
Un vez que el código asíncrono finaliza su ejecución en un momento futuro del tiempo nuestra promesa puede haberse cumplido, con lo que obtendremos el valor esperado, o puede haber fallado con lo que obtendremos un error. En
las promesas, el fallo es propagado en la cadena de promesas (si hay al menos una promesa que depende de otra) siguiendo el mismo efecto burbuja de los eventos.

Manos a la obra

Para seguir este tutorial necesitas:

  • Node.js: Es una plataforma basada en el runtime javascript de Google Chrome (node.js).
  • Grunt: Herramienta que nos simplificará la construcción de la aplicación (Grunt).

Usaremos la librería Q como implementación de CommonJS Promises/A. Para hacer más legible el código del tutorial,
los ejemplos están escritos en coffeescript.

Para el primer ejemplo, crearemos una clase PersonDAO en el que publicaremos tres métodos que simularán un acceso a base de datos (con datos también simulados) y con un comportamiento asíncrono mediante la función setTimeout() de
node para programar la ejecución de una función callback después de «n» milisegundos:

  • getPersonByName: Retorna un objeto Person (nombre y apellidos), en este caso como podéis comprobar siempre retorna el mismo para el ejemplo que queremos construir.
  • save: Simula el almacenamiento en base de datos del objeto Person recibido como parámetro.
  • resolveWithErrorIfIdNotEqualsOne: Para ver cómo funcionan los errores, crearemos este método, el cual resolverá la promesa como fallida si el parámetro pasado es distinto del valor 1, en caso contrario la promesa será cumplida.

Para construir la promesa de nuestro código asíncrono, usaremos un Deferred. Un deferred es un envoltorio de la promesa sobre el que marcaremos en nuestro código asíncrono si la promesa ha sido cumplida o ha fallado. La
diferencia entre un Deferred y una Promesa es, en definitiva, que la promesa es inmutable, es decir, ambos representan una abstracción de lo que ocurrirá en un futuro (en nuestro código asíncrono) pero solamente
en el Deferred es posible cambiar el estado interno de promesa sin cumplir a promesa cumplida o fallo

Nuestro código de la clase PersonDAO quedaría entonces:

Vamos a crear un test para probar nuestro código de la clase Person que obtenga una persona por nombre:

Como se puede comprobar, este código no dista mucho de lo que tendríamos si escribiéremos el test sin promesas y usando la callbacks clásicas de Javascript:

Lo interesante viene cuando tratamos de encadenar diferentes llamadas asíncronas. Vamos a ampliar el ejemplo anterior para que además de obtener la persona por nombre, también le modificaremos la edad, guardaremos el cambio
mediante el método save y, posteriormente, llamaremos al método resolveWithErrorIfIdNotEqualsOne con un ID igual a 1 para que la cadena de promesas no falle:

En este caso, nuestro test ya muestra la legibilidad que nos proporciona el uso de las promesas. Cada paso depués de una promesa resuelta está precedido de la palabra then, que recibe como parámetro el valor
retornado por la promesa anterior en la cadena (si es que ha sido resuelta satisfactoriamente). Este mismo código usando las callbacks clásicas tendría este aspecto:

En caso de fallo, y según lo que se ha comentado previamente de la propagación de los errores (efecto burbuja), la función fail recogerá el fallo de cualquiera de las promesas, y, la funcion fin se ejecutará
siempre al final de la cadena de promesas, por eso hemos transladado la asignación de la variable endTest a ese bloque y evitar código repetido.

Tendremos por tanto un código del estilo try… catch… finally. Vamos a escribir un test para comprobar este comportamiento:

Hasta ahora hemos visto cómo encadenar promesas (chaining) y la propagación. Veamos ahora un ejemplo de promesas que se combinan para formar una única promesa. Para ello vamos a reutilizar la clase PersonDAO y vamos
a modelar una clase Puppy, el comportamiento de nuestra clase Puppy es simple, nuestro perro ladra pero solamente si está contento y para hacer que nuestro perro esté contento hay que acariciarle un rato.

Vamos a escribir un test donde combinaremos dos promesas, por un lado buscaremos una persona por nombre y por otro, esperaremos que nuestro perro ladre, el problema es que como no hemos acariciado a nuestro perro, la
promesa, como un todo, fallará:

Con la funcion all combinamos varias promesas en una sola haciendo que si una de ellas falla el resto no se evalúe. Si queremos esperar por todas las promesas independientemente de si fallan o se cumplen, podemos usar
la función allResolved.

En nuestro test la promesa no se ha cumplido, lástima que nuestro perro no esté contento, por eso hemos recibido un Grrrr! que ha hecho fallar toda nuestra promesa combinada. Vamos a escribir otro test pero
esta vez asegurándonos de acariciar antes a nuestro perro durante un rato (asincronía), así nuestro perro estará contento y nuestra promesa combinada se cumplirá:

Como podéis comprobar, la promesa se ha cumplido y, como cada promesa retorna un valor cuando se resuelve, hemos usado la función spread que asigna un valor a sus parámetros según el orden establecido en el array de promesas pasadas
a la funcion all para comprobar las expectativas de nuestro test.

Conclusiones

Las promesas en Javascript forman un paradigma de programación orientado principalmente a hacer nuestro código más legible y ordenado, evitando el código spaghetti propio de la anidación de callbacks en la programación asíncrona
(pyramid of doom). Existen otras implementaciones de Promesas como por ejemplo rsvp.
En este ejemplo Q lo hace apropiado para desarrollar sobre node.js ya que reconoce su patrón de callbacks, aunque también lo hace compatible para usar en front con el sistema de promesas de jQuery.

Dejar respuesta

Please enter your comment!
Please enter your name here