Llevo ya mucho tiempo programando casi exclusivamente Javascript (excepto por un flirteo con iOS), como para saber que lo primero que hace alguien nuevo en Javascript es algo así:

getUser( function( user ){  
    updateUser( usuario.id, {mail: [email protected]}, function( user ){
        sendConfirmationMail( function(){
            console.log( 'WTF!!!' )
        } )
    } );
} );

Como puedes ver, solo has llamado a tres funciones y ya ibas por tres niveles de anidamiento. Esta estructura es tan común en el mundo de Javascript que hasta le hemos puesto un nombre callback hell. Créeme, es algo que debes evitar hacer a toda costa.

Hay libros muy buenos que te pueden ayudar a mejorar tu estilo de programación asíncrona como Async JavaScript además de futuros lanzamientos que tienen una pinta genial, como JavaScript with Promises.

Aunque nunca está de más una buena lectura, aquí te voy a explicar las bases de la que es, a mi juicio, la mejor librería para trabajar con código asíncrono, Async.

Async es una librería que utiliza programación "funcional". En otras palabras, es una función que recibe funciones como parámetro. Async te va a proporcionar muchas funciones con las que simplificar tu trabajo asíncrono. En este post te voy a explicar las más usadas.

Series

Usarás esta función cuando tengas varias funciones asíncronas que se tienen que ejecutar una detrás de la otra y en donde el orden de ejecución importa, aunque el resultado de cada una de ellas individualmente no. Por ejemplo, si tuvieras que hacer una mudanza tendrías que:

* Empaquetar el contenido de los muebles
* Desmontar los muebles
* Trasladar los paquetes y muebles a la nueva casa
* Montar los muebles
* Colocar el contenido de los muebles

Como ves, cada tarea te va a llevar un tiempo indeterminado; es decir, se trata de tareas asíncronas. Obviamente, el orden de ejecucion es fundamental: las tareas tienen que hacerse en esa secuencia exacta. Por ejemplo, hay que desmontar los muebles antes de trasladarlos y hay que trasladarlos antes de volverlos a montar. Así que vamos a escribir nuestras funciones en Javascript que hacen todo eso:

var empaquetar = function( cb ){  
    // Empaqueto el contenido de los muebles
    cb( null, tiempoEmpaquetando );
};
var desmontar = function( cb ){  
    // Desmonto los muebles
    cb( null, tiempoDesmontando );
};
var traslado = function( cb ){  
    // Traslado las cosas
    cb( null, tiempoTrasladando );
};
var montar = function( cb ){  
    // Monto los muebles
    cb( null, tiempoMontando );
};
var colocar = function( cb ){  
    // Coloco el contenido de los muebles
    cb( null, tiempoColocando );
};

El primer parámetro de llamada del cb siempre es null. Para propósitos didácticos, vamos a asumir que ninguna de nuestras tareas asíncronas falla, aunque en condiciones reales, por desgracia, no siempre será nulo. Como segundo parámetro usamos el tiempo invertido para realizar la tarea, aunque esa es una información que no afecta en absoluto a la próxima tarea de la secuencia.

Las funciones son bastantes descriptivas por sí mismas y te puedes hacer una idea de lo que hacen. Fíjate que todas ellas reciben un parámetro cb: esta es la función que se llamará cuando hayamos hecho el trabajo.

Entonces, mientras que sin la ayuda de Async acabaríamos con algo más o menos así:

empaquetar( function( err, tiempoEmpaquetando ){  
    desmontar( function( err, tiempoDesmontando ){
        trasladar( function( err, tiempoTrasladando ){
            montar( function( err, tiempoMontando ){
                colocar( function( err, tiempoColocando ){
                 console.log( "¡¡La próxima vez contrato a un servicio de mudanzas!!!" );
                } );
            } );
        } );
    } );
} );

gracias a async.serires() tenemos un código infinitamente más elegante:

async.series( [  
    empaquetar,
    desmontar,
    trasladar,
    montar,
    colocar
], function callbackFinal( error, tiempos ){
    console.log( "¡así da gusto mudarse!" );
} );

Fíjate en el último parámetro que tienes en la función callbackFinal. Es un array con la respuesta de cada uno de los callback de cada función. Sería algo así:

[tiempoEmpaquetando, tiempoDesmontando ...] 

waterfall

¿Qué ocurre si además de querer ejecutar las acciones en serie, también necesitas que la salida de la función anterior se convierta en el parámetro de entrada de la función siguiente? Por ejemplo, si necesitaras saber el tiempo que has tardado en empaquetar antes de desmontar.

Primero deberíamos cambiar ligeramente la función desmontar:

var desmontar = function( tiempoEmpaquetando, cb ){  
  if( tiempoEmpaquetando > 8horas ){
      cb( new Error( 'Mañana sigo con la mudanza' ) );
  }
  cb( null, tiempoDesmontando );
};

La función desmontar ahora recibe un nuevo parámetro que es el tiempo que he tardado en empaquetar las cosas. Si he tardado más de 8 horas, continuo otro día con la mundanza.

async.waterfall( [  
    empaquetar,
    desmontar,
    trasladar,
    montar,
    colocar
], function( err, tiempos ){
    if( err != null && err.msg === "Mañana sigo con la mudanza" ){
        console.log( "He gastado todo el día solo empaquetando cosas" )
    }else{
        console.log( "Acabé con la mudanza en un solo día!" );
    }
} );

Nuestro control sobre las tareas asíncronas aquí es muchísimo mayor. Podemos detener todo el proceso en un punto tan solo llamando al callback con su primer parámetro no nulo.

paralelas

Ahora imagínate que tienes una serie de tareas que se pueden hacer todas a la vez. No tienes que esperar a que la anterior esté completada ni necesitas el resultado de la operación anterior para ejecutar la siguiente. Algo así como leer dos webs distintas.

var leerElPais = function( cb ){  
    cb( null, [titulares de El País] );
};

var leerElMundo = function( cb ){  
    cb( null, [titulares de El Mundo] );
};

Vemos dos funciones de ejemplo, que usarías para leer dos periódicos online y el cb devuelves como primer parámetro el error (en caso de haberlo) y en el segundo parámetro los titulares del día en ese periódico.

Como obviamente el acto de leer un periódico no afecta en absoluto a leer el otro son tareas que perfectamente podemos hacer en paralelo.

async.parallel([  
    leerElPais,
    leerElMundo
], function callbackFinal( err, titulares ){
    console.log( titulares );

    // [  [titulares de El Páis], [titulares de El Mundo] ]
});

El funcionamiento y la forma de comportarse la función callbackFinal es muy parecida a la ejecución en series. Solo que esta vez ambas funciones se va a ejecutar a la vez. Independientemente de cuándo acaben, el array resultante titulares siempre estarà en el orden adecuado.

Es importante no utilizar un array demasiado grande de funciones para ejecutarse en paralelo. Para ello, te aconsejo que uses las colas.

Si te ha gustado este post, difunde la palabra. Tampoco dudes en dejar comentarios u observaciones. ¡Gracias! :)

Suscríbete a mi lista de correo

* Campos obligatorios