Javascript Concurrency

Javascipt tiene muchísimas ventajas, se puede hacer practicamente de todo, pero quizás una de sus mayores limitaciones es la ejecución de eventos de manera apropiada cuando se le ordena.

Existen muchas funciones en Javascript que vienen definidas mediante eventos. Algunas de estas funciones se integran en función de la carga del DOM (Document Object Model) como puede ser el Onload, muy importante para iniciar correctamente las variables en un orden establecido.

O una consulta a una base de datos por medio de una comunicación con AJAX, en la que sufriremos la denomindada latencia o retardo de respuesta.

O cuando utilizamos un Framework de animación y render con Javascript como es Three.js que utiliza a su vez WebGL. Este Framework permite integrar renderizados 3D como si de un motor gráfico se tratara, en el que se debe manipular cuidadosamente los tiempos en los que se producen cambios de imagen, color, etc…

Y la cosa se complica un poco más cuando existen frameworks que definen sus propios eventos y de los cuáles no tenemos idea cómo están implementados. Como por ejemplo la lectura de datos de un dispositivo como puede ser un móvil. Ejemplo práctico en el siguiente enlace.

Este post trata en primera instancia sobre la función setInterval. Esta función ejecuta de forma repetida una función definiendo el tiempo de invocación a la misma.

interval1

En la imagen suponemos que la duración de la función es menor que el intervalo de llamada en el bucle. Por lo que en principio no tenemos ningún problema. El problema ocurre, cuando esto no ocurre y no solo tendremos problemas en la interpretación de variables en el programa, sino que además va a ser realmente difícil encontrar el fallo. Si no se sabe que esto puede ocurrir, podemos estar horas y días intentando encontrar la raiz del problema.

interval2

Para ello vamos a ejecutar un programa de una página en la que crearemos 4 ejecuciones en intervalos distintos en el que sumaremos una variable y visualizaremos cómo se ejecutan de forma incorrecta con el tiempo.

<html>
<head>
    <title>SetTimeInterval</title>
    <script type="text/javascript">
        
        var i1 = 1;
        var i2 = 1;
        var i3 = 1;
        var i4 = 1;

        var timer1 = setInterval(function() { document.getElementById('num1').innerHTML=i1++; }, 10);
        var timer2 = setInterval(function() { document.getElementById('num2').innerHTML=i2++; }, 100);
        var timer3 = setInterval(function() { document.getElementById('num3').innerHTML=i3++; }, 1000);
        var timer4 = setInterval(function() { document.getElementById('num4').innerHTML=i4++; }, 10000);

        function clearIntervals(){
            clearInterval(timer1);
            clearInterval(timer2);
            clearInterval(timer3);
            clearInterval(timer4);
        }
    </script>
</head>
<body>
    <h1>SetTimeInterval</h1>
    <input type="button" onclick="clearIntervals()" value="stop">
    <div id ="num1"></div>
    <div id ="num2"></div>
    <div id ="num3"></div>
    <div id ="num4"></div>
</body>
</html>

Este ejemplo contiene definidos 4 intervalos que se repiten cada 10, 100,1000 y 10000ms, de tal manera que los resultados de las variables definidas deben aumentar en la misma proporción, pero cuanto menor es el intervalo mayor error.

SetIntervalFailMinSetIntervalFail10SetIntervalFail


 

La primera prueba se puede ver un ligero desplazamiento a los 2 segundos, en la segunda prueba, el salto se hace más evidente a los 10 segundos ya que los errores se acumulan. Y la última prueba es un desastre, ya que se ha trabajado con otras ejecuciones del navegador que consumían memoria y dejaban ese hilo totalmente desatendido, por lo que se deduce además que el comportamiento puede ser impredecible.

Lo que hace el navegador es encolar todas las funciones que se retrasan, y en caso de que ya este ocupado realizando otras instrucciones, puede pasar olímpicamente de ellas.

El modelo de funcionamiento se explica en la siguiente pagina.

JavascriptConcurrency

 

 

Pila (Stack)

Las llamadas a función forman una pila de frames. Un frame encapsula información como el contexto y las variables locales de una función y cuando una función llama a otra, estas siguen un orden en el que se abren y se cierran todas.

Cola (Queue)

La cola es la que más nos interesa en este sentido. Un programa en ejecución en JavaScript contiene una cola de mensajes, la cual es una lista de mensajes a ser procesados. Cada mensaje se asocia con una función. Cuando la pila está vacía, un mensaje es sacado de la cola y procesado. Procesar un mensaje consiste en llamar a la función asociada al mensaje (y por ende crear una frame en la pila). El mensaje procesado termina cuando la pila está vacía de nuevo.

IMPORTANTE: Una desventaja de este modelo es que, si un mensaje toma mucho tiempo en completarse, la aplicación es incapaz de procesar las interacciones de usuario, tales como clicks o scrolling. El navegador mítiga esta desventaja con el mensaje “un script esta tomando mucho tiempo en ejecutarse“. Una buena práctica es hacer que el procesamiento del mensaje sea corto y, si es posible, dividir une mensaje en varios más.

Montículo (Heap)

Los objetos son colocados en un montículo, el cual, como su nombre lo dice, denota una gran region de memoria, mayormente sin estructura u orden. O sea, la basurilla que sobra por todos lados.

Para todo esto, hay que conocer de antemano, cuánto tiempo máximo de ejecución puede tardar nuestra función y reducirla al máximo. Y aún así va a dar igual porque pueden existir muchos procesos ejecutándose a la vez.

Una manera de solucionar este caso; entre otras; es creando una separación definida entre las llamadas a las funciones. Más información aquí.

timeout


<html>
<head>
    <title>SetTimeOutWarning</title>
    <script type="text/javascript">
        var i = 1;

        function func() {
            i++;
            document.getElementById('num').innerHTML=i;
            timer = setTimeout(func, 2000);
        }
        var timer = setTimeout(func, 2000);

    </script>
</head>
<body>
    <input type="button" onclick="clearTimeout(timer)" value="stop">
    <div id="num"></div>

</body>
</html>

 

Al llamar a setTimeOut se añadirá un mensaje al la cola después de el tiempo especificado como segundo parámetro. Si no hay ningún otro mensaje en la cola, el mensaje es procesado en el momento; sin embargo, si hay mensajes en la cola, el mensaje de setTimeOut tendrá que esperar a que los otros mensajes sean procesados. Por esta razon el segundo parámetro indica el tiempo mínimo tiempo esperado y no es una garantía.