Abstrayendo nuestras Iteraciones
Publicado el Wednesday, November 11, 2015

Empezare diciendo la razón de esta entrada. Todavía tenemos personas escribiendo iteraciones de esta forma:

for(var i = 0; i < arr.length; i++){

}

No es que usar los ciclos for este mal. Pero estar constantemente usando este patrón para recorrer una estructura nos llena nuestro código con el mismo patrón.

var i;
for(i = 0; i < arr.length; i++){ //lo mismo aqui
   //hacemos algo con el arreglo aqui
}

for(i = 0; i < arr.length; i++){ //lo mismo aqui.
   //hacemos otra cosa con el arreglo aquí.
}

¡Nuestra intención puede ser abstraída perfectamente! Vamos a escribir una función que haga esto por nosotros, que recorra el elemento desde 0 hasta el final.

function recorreArreglo(){
    for(var i = 0; i < arr.length; i++){

    }
}

¿Bastante sencillo no? Ahora cada que queramos recorrer el arreglo simplemente llamamos a esta función y nos ahorramos tener que escribir el mismo ciclo una y otra vez. Sin embargo tenemos todavía unos problemas. Primero, nuestra función no hace nada, solo recorre el arreglo. Esto es lo más importante de abstraer, el que hacer con los elementos individuales dentro de nuestro ciclo. Vamos a añadir un parámetro a nuestra función el cual se esperará que sea una función. La función será llamada con el indice y el arreglo.

function recorreArreglo(fn){
    //fn nuestro nuevo parametro (que se espera sea una función)
    for(var i = 0; i < arr.length; i++){
        fn(i, arr);   
    }
}

Un ejemplo sencillo usando nuestra función:

var arr = [1, 2, 3, 4, 5];

recorreArreglo(function(indice, arreglo){
    console.log(arreglo[indice] + 1);
});

Todavía tenemos varios problemas, nuestro siguiente paso sería desacoplar nuestro arreglo dentro de la función. De momento, se espera que exista un arreglo en el ámbito (scope) superior. Vamos a agregar un nuevo parámetro, el cual se esperará que sea un arreglo o un objeto que siga una firma parecida a la de un arreglo.

function recorreArreglo(arr, fn){
    //arr es el arreglo a recorrer!
    for(var i = 0; i < arr.length; i++){
        fn(i, arr);   
    }
}

Ahora podemos trabajar con multiples arreglos, por ejemplo:

var cinco = [1, 2, 3, 4, 5];
var diez = [6, 7, 8, 9, 10];

recorreArreglo(cinco, function(indice, arreglo){
    arreglo[indice] += 1;
});
//cinco == [2, 3, 4, 5, 6]

recorreArreglo(diez, function(indice, arreglo){
    arreglo[indice] *= indice
});
//diez == [6*0, 7*1, 8*2, 9*3, 10*4]

No solo podemos recorrer arreglos. ¡Si el objeto pasado es parecido a un arreglo entonces puede ser recorrido también! Esto puede ser muy útil porque existen varias abstracciones que siguen este patrón, por ejemplo una lista de elementos seleccionados con querySelectorAll:

recorreArreglo(document.querySelectorAll('a'), function(indice, arreglo){
    var ele = arreglo[indice]; //elemento
    ele.addEventListener('click', function(e){
        e.preventDefault(); //¡Todos los links ahora están rotos!
    });
});

Nota como la intención de nuestra función cumple precisamente lo que queremos hacer sin exponer pequeños detalles como es declarar una variable que funcione como contador, incrementar el contador o la forma en la que iteramos el arreglo. Para poder usar nuestra función, lo único que esperamos de esta es que la función se ejecutará por cada indice del arreglo. Como lo hace es irrelevante, dependerá de la implementación que abstrae estos detalles.

Un ajuste más a realizar a nuestra función:

function recorreArreglo(arr, fn){
    for(var i = 0; i < arr.length; i++){
        fn(arr[i], i, arr); //agregamos el valor del arreglo en ese indice
    }
}

Es conveniente agregar el elemento que está siendo recorrido porque la mayoría del tiempo estamos simplemente trabajando con este. De esta forma no tenemos que acceder a el usando los parámetros arreglo e indice. Una gran conveniencia en mi opinión, ya que si solo quieres el valor no necesitas agregar los otros parámetros a la función.

var arr = [1, 2, 3, 4, 5];
recorreArreglo(arr, function(valor){
    console.log(valor + 1);
});
//nuestro primer ejemplo modificado finalmente.

Lo mejor de todo es que Javascript nos ofrece esta misma herramienta, por lo que no tenemos que preocuparnos por implementarla y añadirla a nuestros proyectos. Si el entorno en el que estás trabajando soporta ES5.1 entonces es muy probable que puedas utilizar las siguientes funciones.

forEach

Esta función sigue casi exactamente la firma de nuestra función. La función forma parte del prototipo de los arreglos por lo que solo puede ser utilizada si el objeto tiene en su cadena de prototipos el prototipo del arreglo o si asignas está función al objeto (o en algún punto en su cadena de prototipos).

¿Como difiere el uso de nuestra función?

//nuestra función
recorreArreglo(arr, function(valor, indice, arreglo){

});

//Array.prototype.forEach
arr.forEach(function(valor, indice, arreglo){

}, thisArg); //thisArg es el valor de `this` dentro de la función.

Si es necesario, puedes utilizar lo siguiente para obtener una función mucho más parecida a la de nosotros:

var forEach = Function.bind(Function.call, Array.prototype.forEach);
forEach(arr, function(valor, indice, arreglo){}, thisArg); //muy parecido

Ciclo for - of

Es una buena opción sobre los ciclos for o while con contadores, diría inclusive que mejor que Array.prototype.forEach porque su función es exclusiva sobre iteradores. Es una de las nuevas herramientas que provee ES6, si tu plataforma no soporta el ciclo siempre puedes usar un transpilador que te genere código compatible (aunque en ese caso yo preferiría usar Array.prototype.forEach).

Map, Filter, Reduce

Probablemente estés tentado a realizar los siguientes patrones:

var arr, arr2, suma;

arr = [0, 1, 2, 3, 4];
arr2 = [];
arr.forEach(function(valor){
    arr2.push(valor*2);
});
//arr2 == [0, 2, 4, 6, 8];

arr2 = []
arr.forEach(function(valor){
    if(valor % 2 === 1){
        arr2.push(valor);
    }
});
//arr2 == [1, 3]

suma = 0;
arr.forEach(function(valor){
    suma += valor;
});
//suma == 10

Aquí el patrón recurrente es una variable externa a nuestra función que es usada como acumulador para producir un nuevo resultado. Lo cierto es que tenemos herramientas especificas para trabajar con estos casos.

Map

El primer ejemplo es un claro caso para Array.prototype.map. Tenemos un arreglo y queremos transformarlo a un nuevo arreglo con nuevos valores.

var arr = [0, 1, 2, 3, 4];
var arr2 = arr.map(function(valor){
    return valor * 2;
});

Array.prototype.map es realmente una función muy sencilla de entender. Recorre el arreglo, llama a la función provista con el valor actual y agrega el valor retornado por la función a un nuevo arreglo. No hace falta que el valor regresado sea del mismo tipo que del valor. Puedes transformar el valor en lo que tu quieras.

Filter

El segundo patrón se ajusta más para Array.prototype.filter. Como su nombre lo indica, el propósito de la función es filtrar elementos del arreglo.

var arr = [0, 1, 2, 3, 4];
var arr2 = arr.filter(function(valor){
    if(valor % 2 === 1){
        return true;
    } else {
        return false;
    }
});

La función es muy similar a Array.prototype.map. Recorre el arreglo, llama a la función provista con el valor actual y si el valor retornado es verdadero entonces agrega el valor a un nuevo arreglo. No modifica el valor pero la inserción en el nuevo arreglo es condicionado dependiendo del valor regresado por la función especificada.

Reduce

Creo que es la función más útil para transformar valores. Su función es la de reducir los valores pero realmente puede trabajar los casos de Array.prototype.filter y Array.prototype.map. Lo que significa que los 3 patrones anteriores pueden ser escritos perfectamente con Array.prototype.reduce.

var arr = [0, 1, 2, 3, 4];

var arr2 = arr.reduce(function(acc, valor){
    acc.push(valor*2);
    return acc;
}, []);

var arr3 = arr.reduce(function(acc, valor){
    if(valor % 2 === 1){
        acc.push(valor);
    }
    return acc;
}, []);

var suma = arr.reduce(function(acc, valor){
    return acc + valor;
});

Array.prototype.reduce es una función un poco más difícil de explicar. Te habrás dado cuenta que Array.prototype.forEach, Array.prototype.map y Array.prototype.filter siguen la misma firma. Una función que toma como argumentos: un valor, un indice actual y el arreglo recorrido. Su último argumento en los tres casos es el de thisArg que es el valor que toma this dentro de la función.

Los tres llaman a la función de la misma forma (con los mismos argumentos). Lo único que cambia es el uso que le dan a la función. Array.prototype.forEach simplemente llama a la función, Array.prototype.map agrega el valor retornado de la llamada a la función y Array.prototype.filter usa la función para condicionar la inserción del valor.

Array.prototype.reduce tiene una firma parecida. Toma dos argumentos, una función y un valor. Los parámetros de la función son los mismos con la excepción del primer parámetro. El primer parámetro depende de dos cosas. Depende del segundo argumento provisto y del valor retornado después de la primera llamada. Analicemos los casos.

Si se provee un segundo argumento, el primer argumento de la primera llamada a la función tomara este valor:

var arr = [0, 1, 2, 3, 4];
arr.reduce(function(uno, dos){
    //durante la primera llamada el parámetro uno = []
    //dos = primer elemento del arreglo = 0
}, []);

Si no se provee un segundo argumento, el primer argumento de la primera llamada a la función tomara el primer elemento como valor.

var arr = [0, 1, 2, 3, 4];
arr.reduce(function(uno, dos){
     //Durante la primera llamada el parámetro uno = 0
     //dos = segundo elemento del arreglo = 1
});

Bien, este apenas cubre el primer caso, la primera llamada. ¿Que pasa entonces con el resto de las llamadas? El primer parámetro siempre es el valor retornado de la llamada anterior.

var arr = [0, 1, 2, 3, 4];
arr.reduce(function(anterior, siguiente){
    //anterior es el valor retornado anteriormente
    //siguiente es el valor siguiente duh
    anterior.push(siguiente*2);
    //Si anterior no cambia entonces
    //anterior = anterior = primerValor en todas las llamadas
    //lo que significa que anterior en este caso, es el mismo arreglo
    return anterior;
}, []);

Es probable que la función te provoque un poco de confusión. Mi sugerencia para explotar todos los beneficios de la función es que ejercites el uso de la función, por ejemplo reescribiendo tus funciones que usen Array.prototype.map (solo como ejercicio).

¿Entonces debemos utilizar solo reduce?

Estaría más que contento si dejasen de escribir ciclos cada vez que van a iterar una colección, son detalles que no deberíamos estar repitiendo y solo añaden ruido a nuestras intenciones (que es lo realmente importante). Me daría por satisfecho si los dejarás de lado y optarás por cualquiera de las funciones que he mencionado.

Puedes escribir tus funciones en términos de reduce siempre y cuando tu intención sea clara. A menos que estés generalizando tus funciones reductoras y estés dispuesto a escribir transductores te sugiero que utilices Map y Filter cuando la situación se preste a ese caso en especifico. Los casos de uso son más específicos cuando utilizas estás funciones y su intención es mucho más clara.

¿Transductores, reductores?

Pienso comentar sobre ellos un poco más adelante, pero la idea detrás de ellos es de generalizar todavía más nuestras funciones de tal forma que podemos reutilizar estas funciones para cualquier estructura de datos. Por ejemplo, tendríamos que escribir nuevas funciones para objetos que no sigan el comportamiento de arreglos. Los transductores también nos permiten componer transformaciones en términos de reductores.

Nota Final

Realmente espero que abandonemos esa tendencia a escribir ciclos genéricos (a base de contadores) para iterar nuestras colecciones. Tengo ya un tiempo sin escribir ciclos (fuera de los que he puesto en esta entrada) y noto que mi código es mucho más legible y me puedo enfocar directamente a escribir lo que necesito.


Promesas
Publicado el Tuesday, November 10, 2015

Las promesas han sido un patrón indispensable para trabajar con las funciones asíncronas en Javascript. Simplifican considerablemente el uso de estás funciones y más importante aún nos regresan cierto grado de libertad. Trabajar solo con callbacks es realmente opresivo, como si el programa tuviese control sobre ti cuando debería ser todo lo contrario. Es hora de quitarnos ese peso de encima pero lo primero por hacer es conocer el peso que queremos aliviar.

¿Que hay de malo con solo usar callbacks?

Cuando las utilizas en conjunto con funciones asíncronas estos se vuelven hábiles secuestradores y por si no fuera poco son de los que no dejan ir a sus victimas.

¡Tenemos el flujo de tu programa y no lo vamos a regresar! -Scumbag callback

Es más que obvio que tu quieres el resultado de la operación asíncrona, pero como consecuencia si quieres trabajar con estos resultados lo necesita hacer desde el callback, solo de ahí puedes continuar.

Por ejemplo, si quieres leer un archivo de manera asíncrona, los resultados son solo obtenibles dentro del callback.

leerArchivo('config.ini', function(sitio){
   //Solo desde aquí puedes acceder a la información
});

Continuemos con un ejemplo más complejo para poder apreciar el horror. Ahora imagina que en el archivo de configuración hay un sitio web del cual tenemos que recoger información, comprimir la petición y luego guardar los resultados en el disco duro. Cada una de estas tareas es realizada de manera asíncrona. ¿Como se vería esto?

leerArchivo('config.ini', function(sitio){
    visitaSitio(sitio, function(inf){
        comprimeInformacion(inf, function(infComprimida){
            guardaInformacion('resultados.txt', infComprimida, function(bytesGuardados){
                console.log('Terminamos! Se han escrito ' + bytesGuardados + ' al disco duro');
            });
        });
    });
});

Ahora imagina que este solo es el inicio de tu programa, que hay un montón de tareas asíncronas por realizar y que estás tareas pueden repetirse más de una vez. Una autentica pesadilla. Ni siquiera puedes reutilizar tus funciones porque en cada paso la función necesita conocer que hacer a continuación.

¡Regresa!

Es hora de desligar la continuación de los callbacks y de volver al tema central: las promesas. Las promesas son objetos muy sencillos de describir. Imagina un objeto con dos botones (controles separados), si presionas un botón la promesa hará algo, si presionas el otro botón el objeto hará otra cosa. Es hasta que se haya presionado algún botón que la promesa empieza a trabajar. Este mecanismo es usado para decidir el cuando una tarea asíncrona termina o falla. Por lo general las librerías ofrecen el mecanismo a través de una función con sus respectivos controles (los botones) como parámetros.

var p = new Promise(function(resolve, reject){
    //resolve = botón de logro
    //reject = botón de rechazo
    //¡Realicemos una tarea asíncrona!
    leerArchivo('config.ini', function(err, sitio){
        if(err){
            reject(err);
            //Si ocurrió un error presionar el botón de rechazo
        } else {
            resolve(sitio); 
            //Si la función termino correctamente entonces presionar el botón de logro
        } 
    });
});

Nuestra función ficticia leerArchivo en este caso llama nuestra función con dos argumentos, uno de ellos indica si hay un error y el otro entrega los resultados de no existir un error. Este patrón es muy común para las funciones asíncronas en NodeJS. Lo importante es notar el uso de los controles ofrecidos por la promesa, si ocurre un error la promesa es rechazada, si los resultados son obtenidos correctamente entonces la promesa se logra.

Una vez que la promesa haya sido activada ya sea por que ha sido rechazada o se haya logrado empieza el proceso de llamar las funciones registradas en el objeto. Lo que nos lleva al segundo mecanismo de una promesa. El mecanismo necesario para registrar las funciones a realizar una vez que la promesa este resuelta.

La firma del objeto de una promesa contiene una función then que es la que se encarga de hacer este registro. Toma dos argumentos. El primer argumento debe ser la función a llamar si la promesa se logra, el segundo argumento debe ser la función a llamar si la promesa es rechazada. Ambas funciones pueden tener un parámetro, el cual indica el resultado de la operación o la razón por la cual la operación fallo.

function logro(resultados){
    //resultado de la operación asíncrona.
}

function rechazo(razon){
    //razon por la cual se rechazo la promesa
}

p.then(logro, rechazo); //registra las funciones a realizar

Un aspecto sumamente importante de las promesas (que siguen el estándar), es que then regresa una nueva promesa! Esta nueva promesa marca la transición de la resolución de la función registrada.

¿Como se vería nuestra programa anterior? Lo primero que debemos hacer es promisificar nuestras funciones asíncronas.

function leerArchivoPromesa(archivo){
    return new Promise(function(resolve, reject){
        leerArchivo(archivo, function(err, data){
            if(err){
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

function visitaSitioPromesa(sitio){
    return new Promise(function(resolve, reject){
        visitaSitio(sitio, function(err, data){
            if(err){
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

function comprimeInformacionPromesa(inf){
    return new Promise(function(resolve, reject){
        comprimeInformacion(inf, function(err, data){
            if(err){
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

function guardaArchivoPromesa(archivo, inf){
    return new Promise(function(resolve, reject){
        guardaArchivo(archivo, inf, function(err, data){
            if(err){
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

Existen librerías que ya trabajan con promesas exclusivamente, por lo que no es necesario que tu hagas esto con las funciones provistas. Incluso existen librerías de promesas que promisifican tus objetos y módulos como BlueBird. Sin embargo, creo que es importante que conozcas como promisifcar las funciones, por eso he incluido el ejemplo completo.

Una vez que tengamos nuestras funciones que regresen una promesa, componer el programa es realmente sencillo.

leerArchivoPromesa('config.ini')
    .then(visitaSitioPromesa)
    .then(comprimeInformacionPromesa)
    .then(function(inf) { return guardaArchivoPromesa('resultados.txt', inf); })
    .then(function(bytesEscritos){
        console.log('Se han escrito ' + bytesEscritos + ' al disco duro!');
    });

Como puedes ver, ya no estamos trabajando dentro de callback anidados. La continuación no esta dada por las funciones registradas sino por la promesa en sí. ¡Recuperamos una gran parte del control a través de la promesa!

Detalles de las promesas

¿Cuando se invocan las funciones registradas?

La promesa empieza a llamar las funciones registradas cuando la promesa es resuelta. Si la promesa es resuelta y no hay ninguna función registrada, no se llama ninguna función. Sin embargo, al momento de registrar una función se realiza una verificación del estado de la promesa, si la promesa se ha resuelto antes que la función haya sido registrada entonces la función es llamada. Es importante recordar que las funciones son llamadas de manera asíncrona (de acuerdo al estándar).

¿Como es que funciona regresar una promesa?

En el ejemplo anterior tenemos un caso especial para nuestras funciones registradas con then, nuestras funciones regresan una promesa. Lo que ocurre es que la promesa a la hora de resolver el valor retornado de nuestra función registrada con then reconoce que el valor retornado es del tipo de una promesa. En lugar de resolver la promesa con otra promesa, resuelve la promesa con el valor resuelto de la promesa retornada.

var p = new Promise(function(resolve){
    resolve(true); //resolvemos la promesa con true.
});

leerArchivo('config.ini').then(function(){
    return p;
}).then(function(valor){
   //valor no es p, sino true
});

Esto nos permite elaborar cadenas de promesas que se desarrollan en secuencia muy fácilmente.

¿Que otras ventajas nos ofrecen las promesas?

¡Podemos realizar múltiples operaciones en paralelo fácilmente! Por ejemplo, usando Promise.all (un método bastante común en las librerías de promesas):

var archivos = ['config1', 'config2', 'config3', 'config4', 'config5']
Promise.all(archivos.map(function(archivo) { return leerArchivo(archivo); }))
    .then(function(archivos){
        //funcion que se ejecuta cuando todos los archivos están cargados.
        //Los archivos se encuentran dentro del arreglo `archivos` (parametro)
    });

¡Podemos componer funciones fácilmente! Habiamos dicho anteriormente que nuestro programa apenas es el inicio:

function guardaWeb(){
     return leerArchivoPromesa('config.ini')
        .then(visitaSitioPromesa)
        .then(comprimeInformacionPromesa)
        .then(function(inf) { return guardaArchivoPromesa('resultados.txt', inf); })
        .then(function(bytesEscritos){
            console.log('Se han escrito ' + bytesEscritos + ' al disco duro!');
        });
}

guardaWeb().then(function(){
     //Hemos guardado la web aqui, podemos continuar trabajo desde aquí.
});

¡Usas promesas nos permite trabajar con errores de una manera más natural! Solo con callbacks tenemos el problema que en cada paso tenemos que estar revisando los errores.

guardaWeb().catch(function(err){
   //Si algunas de las promesas es rechazada debido a un error, el error acaba aquí
   //no se ejecutan ninguna de las acciones establecidas una vez se lanzado el error
   console.log(err);
});

El lado negativo de las Promesas

Creo que el problema más grande que tienen las promesas es que capturan todos los errores que ocurran dentro de la cadena de promesas, lo que significa que podemos tener errores silenciosos. Tu programa puede fallar completamente y no dejarte ninguna pista de donde ha fallado. Por eso es importante siempre terminar la cadena con un catch o con done. Algunas librerías tratan el tema de una manera más sensible y expulsan errores no capturados después de un tiempo.

Otro posible problema es quizás es el mal uso del mecanismo para resolver las promesas. Si la promesa no se logra o se rechaza, la promesa no puede continuar. No hay una forma precisa de encontrar este tipo de problemas (algunos navegadores por lo menos lanzan una advertencia) aunque deberían ser extremadamente raros de ocurrir.

Librerias de Promesas

Bluebird

Q

RVSP

Otras implementaciones

Tu navegador probablemente implemente las promesas de ES6, si es un navegador reciente.


Funciones Asíncronas en Javascript
Publicado el Friday, November 6, 2015

En primer lugar te estarás preguntando, ¿Que significa que una función trabaje de manera asíncrona? Imagina que eres un programador al cual le han dado la tarea de construir un sitio web junto a tu compañero. Tu compañero de trabajo está encargado de programar el código del lado del servidor y tu trabajas en código que se ejecuta en el cliente. Llega un punto en el que necesitas preguntarle a tu compañero acerca de alguna petición HTTP y tu compañero está realmente ocupado con sus deberes.

Sería una perdida de tiempo detener todo lo que haces solo para esperar la respuesta de tu compañero, es por eso que sigues trabajando en tus deberes hasta que tu compañero se desocupe. La situación es análoga a la diferencia entre código asíncrono y síncrono. De forma asíncrona, se entiende que el resultado de la tarea puede ocurrir después y que no es necesario esperar por la respuesta para seguir continuando. Su contraparte es todo lo contrario.

Si has estado programando en Javascript es probable que ya hayas interactuado con herramientas que se presentan en forma asíncrona. Los temporizadores son buenos ejemplos de funciones asíncronas y son sencillas de entender. Los temporizadores (timers) en Javascript son funciones que toman una función y la ejecutan de acuerdo a una agenda, un intervalo o después de cierto tiempo. Observemos un ejemplo de temporizador:

function digaHola(){
   alert('Hola mundo');
}

setTimeout(digaHola, 1000);

La función setTimeout es la función temporizadora que se encarga de agendar la ejecución de la función provista como argumento para ejecutarse dentro de 1000 milisegundos. Probablemente estés pensando que la función es muy parecida a sleep en otros lenguajes en la que el programa espera o se "duerme" por la cantidad provista. Lo cierto es que hay una gran diferencia: la función no bloquea la ejecución del programa, por lo que se puede seguir trabajando inmediatamente después de la llamada.

function digaHola(){
   alert('Hola mundo');
}

function continuaTrabajando(){
   //seguimos trabajando
}

setTimeout(digaHola, 1000);
continuaTrabajando();

Es aquí donde la mayoría cometen la equivocación de asumir que Javascript está ejecutando la función digaHola en paralelo. No es el caso, el código sigue ejecutándose sobre el mismo hilo. La función setTimeout agenda la función para ejecutarse después. ¿Cuando? Se podría pensar que dentro de 1000 mili segundos (1 segundo) pero la respuesta correcta es que depende. De una cosa pueden estar seguros, la función jamás se llamará mientras que siga habiendo algo ejecutándose (en el stack). Por ejemplo:

setTimeout(digaHola, 1000);
for(var i = 0; i < 10000000; i++);

digaHola jamás es llamado antes de que el ciclo termine. El ciclo puede tardarse lo que sea, 1 segundo, 1 minuto, 1 hora, etc. y a pesar de que nuestro temporizador se le ha indicado que inicie en 1 segundo, este no puede llamar la función. Sabiendo esto, les pregunto a ustedes:

¿Cual creen que sea el resultado de imprimir la variable resultado en el siguiente script?

var resultado;
setTimeout(function(){
    resultado = 1;
}, 0);
console.log(resultado);

Este tipo de patrón lo he visto todo el tiempo, aunque usando diferentes funciones asíncronas. El resultado esperado en este caso es 1, pero la realidad es que la función no se ha ejecutado todavía porque todavía hay algo por ejecutarse (la linea del console.log). El resultado inevitablemente es undefined.

¿Es entonces el temporizador la siguiente tarea a ejecutar? En este caso sí, pero es importante recalcar que los navegadores pueden programar otras tareas (tasks) o micro tareas (microtasks) y cada una de estas puede tener una prioridad diferente.

Cuando la función dada termina (en este caso nuestro pequeño script), entramos a lo que se conoce como el ciclo de eventos (Event Loop) y se decide cual es la siguiente función a ejecutar. El ciclo está constantemente revisando por nuevos eventos y cuando ocurren se lanzan las acciones asociadas. El ciclo no lanza las tareas de forma paralela, espera a que una función termine para seguir escuchando por nuevos eventos y lanzar las nuevas tareas.

Como el ciclo de eventos está esperando a que la función termine resulta especialmente dañino usar los ciclos infinitos usuales:

while(true){
   //toda la lógica de tu aplicación aquí:
}

Se dice entonces que estás bloqueando el ciclo de eventos. Ya que el ciclo está esperando que tu función termine. Ninguna tarea se ejecutará después de esto. En el navegador esto significa que no puedes interactuar con el documento y el documento no puede ser actualizado (aparecerá congelado). Creo que no hace falta decir que este comportamiento es totalmente indeseado. Es la muerte literal de la aplicación. El ciclo de eventos necesita fluir, seguir evaluando los eventos tan rápido como ocurran. De ahí la frase:

Don't block the Event Loop!

¿Como trabajar con estas funciones asíncronas?

Tienes algunas opciones para trabajar con ellas, ya has visto una de ellas: callbacks (llamadas de regreso o retrollamadas). ¿Que son estas callbacks? Una función a ser llamada en algún punto. No hace falta que la tarea sea asíncrona para ser llamada, es a discreción de la función que llama dictar el cuando y el como debe ser llamada la función. El comportamiento es similar a lo que esperarías de una conversación telefónica que necesita ser reanudada después: "Llamame después de que termines." La persona termina la tarea y la conversación se reanuda.

Ya que tocamos el tema de una conversación telefónica, trabajemos un ejemplo ficticio de una conversación:

decir('Hola, ¿Como estás?');
esperaRespuesta(contestacion);

function contestacion(respuesta){
   if(respuesta === 'Bien.'){
      decir('¡Que bueno que estés bien!');
   } else {
      decir('¡Se pondrán mejor las cosas!');
   }
}

A diferencia de nuestros ejemplos anteriores, nuestra función a pasar toma un argumento. Recuerda que es a discreción de la función que recibe el callback el como llamar la función. La única forma de verificar que argumentos toma el callback es revisando la función e inspeccionando la llamada. Usualmente, los argumentos de estas funciones están documentados, por lo que siempre es bueno revisar la documentación. En este ejemplo tan sencillo, la función es simplemente llamada con la respuesta de la persona.

Trabajar simplemente con callbacks, nos crea un problema. Si queremos seguir trabajando después de la respuesta, tiene que ser dentro del mismo callback. Lo que significa que el flujo del programa pasa a manos de la función y una vez que usemos otra función asíncrona, el flujo del programa pasa al nuevo callback. Este patrón es conocido como Continuation-Passing Style y nos lleva a escribir código así:

esperaRespuesta(primeraContestacion);

function primeraContestacion(saludos){
    decir('Hola');
    esperaRespuesta(segundaContestacion);
}

function segundaContestacion(respuesta){
    if(respuesta === '¿Como estás?'){
        decir('Bien, y tu?');
    }
}

O si lo prefieres ver con funciones anónimas:

esperaRespuesta(function(saludos){
    decir('Hola');
    esperaRespuesta(function(respuesta){
        if(respuesta === '¿Como estás?'){
            decir('Bien, y tu?');
        }
   });
});

Para seguir continuando con el programa, necesitamos seguir delegando el control a nuevos callbacks. De momento tenemos solo dos callbacks pero a medida que tu programa crece es muy probable que no quieras seguir el flujo del programa desde tus callbacks.

¡No intentar en casa!

Muchos intentan escapar el flujo retornando un valor:

var respuesta = esperaRespuesta(function(resp){
    return resp;
});

Esto es incorrecto, porque la función asíncrona agenda la ejecución del callback, no la ejecuta inmediatamente y el valor regresado es es el resultado de agendar la función (y no de ejecutarla) que en muchos casos es simplemente undefined. Otros intentos incluyen el patrón anteriormente mencionado (que también es incorrecto):

function esperaRespuestaSincrona(){
    var resp;
    esperaRespuesta(function(respuesta){
        resp = respuesta;
    });
   return resp;
}

var respuesta = esperaRespuestaSincrona();

Si decides usar callbacks entonces por favor no uses ninguno de estos dos patrones.

Promesas

Las promesas son objetos que representan el valor eventual de una operación asíncrona. No substituyen los callbacks pero te permiten "proxificar" el flujo de tu programa. De forma que puedes hacer:

var saludos = esperaRespuesta().then(function(respuesta){
     decir('Hola, ¿Como estás?');
     return esperaRespuesta();
});

var conversacion = saludos.then(function(respuesta){
     if(respuesta === 'Bien'){
         decir('¡Que bien!');
     }
});

Nota como nuestros callbacks no saben nada de que se hará después que la operación asíncrona termine, están completamente desligados. La continuación a la siguiente función no esta dada por el callback sino por la promesa. Es la promesa quien organiza la continuación.

Es más fácil si visualizas el estilo de continuación de esta forma:

function primera(){
   segunda();
}

function segunda(){
    //tercera(); etc
}

Y el estilo que sigue una promesa:

function primera(){
    //regresa una promesa
}

function segunda(){
   //puede regresar una promesa
}

primera.despues(segunda);

No solo nos permite desligar la continuación, sino delegar la promesa a otras funciones para que estas sigan la continuación. Por ejemplo:

function preguntaComoEsta(){
    decir('Hola, ¿como estás?');
    return esperaRespuesta(); //nuestra promesa
}

function respondeRespuesta(respuestaPromesa){
    respuestaPromesa.then(respuesta){
        if(respuesta === 'Bien'){
           decir('¡Excelente!');
        } else {
           decir('¡Todo ira mejor!');
        }
    }
}

var respuesta = preguntaComoEsta();
respondeRespuesta(respuesta);

Nota las últimas dos lineas. Es idéntico a como lo escribirías si fueran funciones síncronas.

Otros

Con la llegada de generadores en ES6 se pueden realizar cosas interesantes en conjunto con las promesas hay incluso unas ideas muy interesantes propuestas para ES7 (funciones async). También recomendaría la lectura de CSP y de algunas librerías como js-cp.