viernes, 12 de febrero de 2016

Promesitas titas titas...!

Casi cualquiera que haya trasteado un poco con JavaScript y principalmente con NodeJS es casi seguro que se ha encontrado con el patrón promesa y lo ha usado para quitarse anidaciones infinitas e infiernos de callbacks en operaciones asíncronas.

El patrón en sí mola y es una buena idea aunque, como todo, también depende de la implemetación que se use para que te de más o menos funcionalidades.

¿Pero cómo funciona por dentro este patrón?

Implementar las fucionalidades básicas de este patrón en JS es bastante fácil debido a su naturaleza de hilo único y modelo de eventos. En otro lenguaje, donde puedes tener problemas de concurrencia como bloqueos mutuos y condiciones de carrera, es más complejo.

Aun así. no voy a implementar nada en ningún lenguaje en concreto, simplememente voy contar como funciona en un entorno sin problemas de cocurrencia para quitar ruido y los requisitos para que todo funcione. Y todo ello sin florituras y para todos los públicos.

La promesa más sencilla del mundo simplemente es una entidad que mantiene el estado de una operación y acciones a realizar cuando se ha completado o fallado.

En pseudocódigo sería algo así:

Promesa
     estado
     accionAlCompletar
     accionAlFallar
Fin promesa

Lo normal es que una operación asíncrona retorne una promesa en estado "en progreso" en cuanto se lance, también mantendrá una referencia a esa promesa retornada.

Una vez que la operación asíncrona ha terminado con éxito o con fallo, esta operación asíncrona actualizará el estado de la promesa.

La propia promesa lanzará las acciones que tiene guardadas según el estado que se le asigne. Si se le pone estado "completada" ejecutará la "accionAlCompletar" y si es fallida ejecutará "accionAlFallar".

Promesa
     estado <-- #en progreso, #completada, #fallida
     accionAlCompletar
     accionAlFallar
     setAccionAlFallar(accion)
     setAccionAlCompeltar(accion)
     setEstado(nuevoEstado){
         estado = nuevoEstado;
         if estado = completada { call accionAlCompletar }
         if estado = fallida  { call accionAlFallar }
Fin Promesa

OperacionAsincrona
    miPromesa
    DoSomethingAsync() --> miPromesa(en progreso) //retorna una promesa
    :onComplete {call miPromesa.setEstado(completada)}
    :onFail {call miPromesa.setEstado(fallida)}
Fin OperacionAsincrona

Esto nos permite coordinar operaciones asíncronas, ya que si la accionAlCompletar es otra operación asíncrona, ésta no se ejecutará hasta que la anterior haya finalizado y cambie el estado de la promesa.

promesa <-- call (OperacionAsincrona)
call promesa.setAccionAlCompletar(OtraOperacionAsincrona)

Un momento, un momento... la operación asíncrona puede haber acabado antes de asignarle la operación que debe realizar al acabar y por lo tanto ya se ha cambiado el estado de la promesa. Nunca se lanzaría la otra operación asíncrona.
Bien visto colega. Lo que ocurre aquí es que, en un entorno como JavaScript, sólo tenemos un único hilo ejecutando nuestro código; así que mientras tengamos código debajo de la llamada a la operación asíncrona nunca se ejecutará la función de cambar el estado de la promesa y viceversa; sino que se quedará pendiente en el loop de eventos del sistema y se ejecutará cuando el hilo se quede libre.

Pongo un ejemplo con los posibles flujos:

1. Se lanza operación asíncrona y retorna una promesa en estado pendiente.
2. Se asigna un callback a la promesa y ésta lo único que hace es guardarlo ya que está en estado pendiente.
3. Nuestro hilo de código se queda parado porque ya no tiene nada que hacer.
4. La operación asíncrona termina y, como nuestro hilo de código esta libre, se ejecuta el cambio de estado de la promesa y, en consecuencia, se lanza el callback asignado a ese estado.

1. Se lanza operación asíncrona y retorna una promesa en estado pendiente.
2. La operación asíncrona termina y, como nuestro hilo está ocupado con más líneas de nuestro código (asignando un callback en este caso), se guarda en el loop de eventos del sistema el trabajo pendiente de cambiar de estado la promesa.
3. Se asigna un callback a la promesa y ésta lo único que hace es guardarlo ya que está en estado pendiente.
4. Nuestro hilo de código termina y se queda libre por lo que pasa a ejecutar el evento pendiente de la operación asíncrona.
5. Se ejecuta el cambio de estado de la promesa y se lanza el callback asignado.
¿Y si se necesita establecer el callback después de algún otro evento y el evento de la operación asíncrona se ejecuta antes? 
Pues es verdad, que despiste el mío ¿verdad? Bueno, un toquecito por aquí, un toquecito por allá y listo.

Promesa
     estado <-- #en progreso, #completada, #fallida
     accionAlCompletar
     accionAlFallar
     setAccionAlFallar(accion) {
          accionAlFallar = accion
         if estado = fallida  { call accionAlFallar }
     }
     setAccionAlCompeltar(accion){
         accionAlCompletar = accion
         if estado = completada { call accionAlCompletar }
     }
     setEstado(nuevoEstado){
         estado = nuevoEstado;
         if estado = completada { call accionAlCompletar }
         if estado = fallida  { call accionAlFallar }
Fin Promesa

Y así, a grandes (muy grandes) rasgos tenemos la promesa más básica que podemos implementar.

No hay comentarios:

Publicar un comentario