Pero primero, ¿porqué es necesaria la programación asíncrona?, el problema es que javascript es single threaded (se ejecuta sobre un solo hilo), lo que imposibilita realizar más de una tarea a la vez. Dado que las tareas se ejecutan una tras otra y que se tiene un entorno single threaded, el hecho de que una tome demasiado tiempo para realizarse evita que las demas tareas se ejecuten y es por esto que los procesos que comunmente tardan (I/O) se ejecutan asíncronamente, para evitar que se tenga que bloquear la realización de otras tareas.
Un ejemplo de un proceso que toma demasiado tiempo en ejecutarse y bloqueara la ejecución del programa hasta que finalice:
function tareaCorta(param) { console.log('Esta ejecutandosé la tarea corta ' + param); } function tareaLarga(param) { console.log('Esta ejecutandosé la tarea larga ' + param); for(var i = 0; i <= 5000000000; i++) { // bucle que tarda demasiado en ejecutarse } console.log('Terminó la tarea larga ' + param); } tareaCorta(1); tareaCorta(2); tareaLarga(3); tareaCorta(4); tareaCorta(5);
Y como se puede observar, el programa se detiene para ejecutar la tarea larga (que es simplemente un bucle muuuuuy largo):
Ejemplo de la ejecución del código |
TAREA1();
RESULTADO_TAREA_2 = TAREA2();
TAREA3(RESULTADO_TAREA_2);
TAREA4();
Mensajes asíncronos y síncronos |
Para conceptualizar mejor la idea, es necesario estudiar el siguiente gráfico del mismo pseudocógido anterior:
Ejecución asíncrona vs síncrona |
Ahora, algunos conceptos que son de importancia:
- call stack (pila de llamadas): mantiene un listado de las funciones que están ejecutandose. Si la funcion1 llama a la funcion2, entonces en el callstack está ejecutandose la funcion2 y está en espera en el mismo call stack la funcion1.
- event queue (cola de eventos): es una cola que mantiene mensajes que referencian a las funciones que estan en espera de ser puestas en el call stack para ser ejecutadas. Las tareas son agregadas a la cola de eventos por web apis que corren en paralelo con el entorno de javascript.
- web api: agregan tareas al event queue, por ejemplo:
- timers: programan tareas para ser agregadas al event queue en un determinado tiempo.
- DOM event handlers: agrega interacciones del usuario, como eventos del mouse o del teclado al event queue.
- network requests: las peticiones por la red son procesadas asincronamente y devuelven resultados agregando tareas al event queue.
- event loop (ciclo de eventos): cuando el call stack esta vacio, toma la primera tarea del event queue y la procesa. Las tareas que quedan en la cola (queue) esperan hasta que el call stack esta limpio nuevamente. Este ciclo se llama event loop.
Ejemplo del event queue y el call stack:
function func1(){ console.log('tarea1'); } function func2(){ console.log('tarea1'); } func1(); func2();
Ejemplo del call stack y del event queue |
function func1(){ func2(); } function func2(){ console.log('Tarea completadas'); } func1();
Ejemplo del event queue y call stack |
Es posible que las apis web agreguen los resultados de sus procesos como tareas a la cola de eventos (event queue). Estas tareas son definidas por funciones callbacks que se pasan a las apis web.
- callback functions: Son funciones que se pasan como argumentos para ser ejecutadas en un punto futuro del tiempo.
- anonymous callbacks: Son funciones creadas sin nombre. Son útiles cuando el callback solamente necesita ser ejecutado una vez.
Como ejemplo, veamos una función callback con nombre y una función callback anónima:
var numeros = [1, 2, 3, 4, 5]; console.log(numeros); // callback anónimo, multiplica por dos var numerosDuplicados = numeros.map(function(valor) { return valor * 2; }); console.log(numerosDuplicados); function multiplicaPorTres (valor) { return valor * 3; } // callback con una función que tiene un nombre var numerosTriplicados= numeros.map(multiplicaPorTres); console.log(numerosTriplicados);
Timers
Son funciones nativas de javascript que permiten retrasar la ejecución de instrucciones de la manera que necesitemos. El tiempo que necesitamos que se retrasen se calcula asíncronamente.
Son funciones nativas de javascript que permiten retrasar la ejecución de instrucciones de la manera que necesitemos. El tiempo que necesitamos que se retrasen se calcula asíncronamente.
- setInterval() se utiliza para programar una tarea recurrente en el tiempo
- clearInterval() se utiliza para detener una tarea que se creó con setInterval()
- setTimeout() se utiliza para programar una tarea en un determinado tiempo
Ahora como ejemplo ejecutaremos la misma tarea del primer ejemplo, pero ahora la primera tarea se retrase 1000 milisegundos (1 segundo).
function tareaCorta(param) { console.log('Esta ejecutandosé la tarea corta ' + param); } function tareaLarga(param) { console.log('Esta ejecutandosé la tarea larga ' + param); for(var i = 0; i <= 5000000000; i++) { // bucle que tarda demasiado en ejecutarse } console.log('Terminó la tarea larga ' + param); } setTimeout(function(){ tareaCorta(1) }, 5000); tareaCorta(2) tareaLarga(3); tareaCorta(4); tareaCorta(5);
El resultado de la ejecución es el siguiente:
Ejecución con timeout |
El timer funciona correctamente, la tarea se ejecuta cinco segundos despues. Ahora, veamos otro ejemplo un poco mas extraño:
Como podemos observar, ahora establecimos el timeout a 0, o sea que la tarea se programa para ejecutarse de inmediato. Veamos el resultado:
Lo interesante del ejemplo es que la tareaCorta(1) aún se ejecuta al final, pero ¿porqué, si su timeout (tiempo de espera) es cero?, el problema es el orden en el que se agregan las tareas al callstack, veamos (Revisar el diagrama de Ejecución asíncrona vs síncrona que se agrego en uno de los puntos anteriores):
function tareaCorta(param) { console.log('Esta ejecutandosé la tarea corta ' + param); } function tareaLarga(param) { console.log('Esta ejecutandosé la tarea larga ' + param); for(var i = 0; i <= 5000000000; i++) { // bucle que tarda demasiado en ejecutarse } console.log('Terminó la tarea larga ' + param); } setTimeout(function(){ tareaCorta(1) }, 0); tareaCorta(2) tareaLarga(3); tareaCorta(4); tareaCorta(5);
Como podemos observar, ahora establecimos el timeout a 0, o sea que la tarea se programa para ejecutarse de inmediato. Veamos el resultado:
Resultado de la ejecución |
- Se "programa" la ejecución del timeout en 0 segundos (la tarea ya esta lista para agregarse, pero estamos esperando al siguiente ciclo de eventos)
- Se agrega al call stack la tareaCorta(2) para su ejecución
- Se agrega al call stack la tareaLarga(3) para su ejecución
- Se agrega al call stack la tareaCorta(4) para su ejecución
- Se agrega al call stack la tareaCorta(5) para su ejecución (ya no existen más elementos para ejecutarse en el call stack así que ahora se agrega la tarea que se programó antes)
- Se agrega al call stack la tareaCorta(1) para su ejecución
Se programa el timeout, se agregan al call stack las cuatro tareas y luego se agrega la tarea que esta esperando a ser ejecutada por el timeout con 9 milisegundos de espera.
DOM event listeners
Los "oyentes" de eventos del DOM ocurren paralelamente al cógido javascript ejecutado (asíncronamente). Los event listeners detectan un evento y lo agregan al event queue para que el evento sea ejecutado en el futuro. En caso de tener muchos eventos que suceden uno tras otro, todos se agregan en el orden en que ocurren a la cola de eventos (event queue). Algunos eventos que pueden suceder son: keypressed, keyup, mouseenter, mouseleave, etc.
Ahora un ejemplo de el manejo de eventos con javascript super sencillo (lo importante es recordar que los listeners siempre estan escuchando y agregarán el evento al event queue en cualquier momento). Crear en una carpeta el archivo example.html y example.js. El código del archivo example.html es:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Eventos</title> </head> <body> <div id="div-rojo" style="background-color: red;height:200px;"></div> <br /> <div id="elemento"></div> <script src="example.js"></script> </body> </html>
Simplemente creamos dos divs, y agregamos el javscript con el nombre "example.js". Ahor el código javascript:
document.getElementById('div-rojo') .addEventListener('mouseenter', function(){ document.getElementById('elemento') .innerHTML = 'El mouse ha entrado al div rojo'; }); document.getElementById('div-rojo') .addEventListener('mouseleave', function(){ document.getElementById('elemento') .innerHTML = 'El mouse ha salido del div rojo'; });
Con esto, obtenemos el primer div (div-rojo), y le agregamos un listener para el evento mouseenter, cuando el evento suceda, obtenemos el otro div y escribimos el mensaje El mouse ha entrado al div rojo. También, obtenemos el primer div (div-rojo) y le agregamos el evento contrario (mouseleave), pero para que se note el cambio modificamos el mensaje del otro div diciendo que El mouse ha salido del div rojo.
Ejemplo de ejecución (no tomen en cuenta las manchas, es para mantener bajo el tamaño del gif XD) |
No hay comentarios:
Publicar un comentario