RSS Feed

Ejecuciones Asincrónicas de funciones

5

noviembre 30, 2011 by - @pjnovas

En javascript no tenemos la posibilidad de hacer un new Thread() como en otros lenguajes, pero podemos simular ese comportamiento y es ahi donde entran las funciones setTimeout() y setInterval():

.setTimeout ( función , tiempo )
Ejecuta una función al X tiempo, siendo X milisegundos
.setInterval ( función , tiempo )
Ejecuta una función cada X tiempo, siendo X milisegundos

Así de simple, setTimeout va a ejecutar la funcion que le enviemos por parámetro en el tiempo que le digamos y setInterval ejecuta la función n veces esperando por cada ejecución el tiempo que le digamos.

Supongamos que necesitamos realizar una llamada a ajax cada x tiempo y refrescar una lista de entidades:

function obtenerDatos(){
    // lógica para obtener y mostrar los datos

    setTimeout(function(){
        obtenerDatos();
    }, 10000);
}

obtenerDatos();
function obtenerDatos(){
    // lógica para obtener y mostrar los datos
}

var relojito = setInterval(function(){
        obtenerDatos();
    }, 10000);

En el primer caso llamo a la función y esta se llama a si misma cada 10 segundos, pero en el segundo caso inicio una llamada a la funcion con un intervalo de 10 segundos en cada llamada.

En el segundo caso (setInterval) la función va a ser llamada por primera vez a los 10 segundos de setear el intervalo.

Cual utilizar y cuando va a depender de lo que queramos hacer, pero es importante entender sus diferencias al momento de elejir.

Y si queremos dejar de llamar a la funcion en algún momento dado?, supongamos a la 5ta llamada. Como sería para cada caso?:

var cantidadDeLlamadas = 0;
function obtenerDatos(){
    cantidadDeLlamadas++;

    // lógica para obtener y mostrar los datos

    if (cantidadDeLlamadas < 5) {
        setTimeout(function(){
             obtenerDatos();
        }, 10000);
    }
}

obtenerDatos();
var cantidadDeLlamadas = 0;
function obtenerDatos(){
    cantidadDeLlamadas++;

    // lógica para obtener y mostrar los datos

    if (cantidadDeLlamadas === 5)
        clearInterval(relojito);
}

var relojito = setInterval(function(){
        obtenerDatos();
    }, 10000);

Genial, en el primer caso simplemente comprobamos la cantidad y no llamamos al setTimeout, pero en el segundo realizamos un clearInterval() de la variable donde guardamos el intervalo. Bueno, hay que tener algo muy importante en cuenta para el clearInterval. Qué pasa si hago lo siguiente?:

var cantidadDeLlamadas = 0;
function obtenerDatos(){
    cantidadDeLlamadas++;

    // lógica para obtener y mostrar los datos

    if (cantidadDeLlamadas === 5)
        clearInterval(relojito);
}

var relojito = setInterval(function(){
        obtenerDatos();
    }, 10000);

// creo otro setInterval y lo guardo en la misma variable que el anterior 
relojito = setInterval(function(){
        obtenerDatos();
    }, 10000);

Lamentablemente al realizar el segundo setInterval perdimos toda referencia al primero, pero ambos siguen ejecutandose, por lo que cuando ejecute el clearInterval(relojito) solo va a detener el segundo intervalo, quedando el primero corriendo infinitamente sin posibilidad de detenerlo.

Ahora que sabemos esto, vamos a hacer un cambio en el código:

var cantidadDeLlamadas = 0;
var relojito;
function obtenerDatos(){
    cantidadDeLlamadas++;

    // lógica para obtener y mostrar los datos

    if (cantidadDeLlamadas === 5)
        clearInterval(relojito);
}

clearInterval(relojito);
relojito = setInterval(function(){
        obtenerDatos();
    }, 10000);

Simplemente realizo un clearInterval antes de asignarle uno nuevo, de esta manera me aseguro de no pisar un intervalo anterior al momento de crear uno. Realizar un clear antes de crear un nuevo intervalo es una recomendación personal, pero creanme que puede ser muy frustrante descubrir un problema de ese tipo.


  • Pingback: Creando y utilizando callbacks | fernetjs

  • Lucas Romero

    Algo que puede pasar es que la función a ejecutar tenga un tiempo dinámico de ejecución y que llegado el caso de que demore más que el intervalo haga que la nueva llamada espere hasta completar la ejecución de la anterior. Es correcto? En este caso no tendríamos ciclos fijos sino que serían cambiantes y dependiendo del caso se podría convertir en un problema.
    Algo para agregar: clearTimeout() sería el que se usa para setTimeout()
    Buen post!

    • Anónimo

      Exacto, si vos escribís un setTimeout, setInterval o lo que sea, pero también escribís código bloqueante, entonces vas a tener retrasos, que dependiendo de qué estés manejando, te pueden impactar bastante. No hay que olvidarse que en el browser el js corre en un solo proceso y es muy común que eso te pase si no le prestás atención a ese tipo de cosas (por eso es que se implementaron los webworkers, que te dejan ejecutar código en paralelo siempre que no modifique ni dependa de la UI). En Node y en muchas otras implementaciones va a pasar lo mismo, pero también hay formas de no bloquear el proceso en el que estás corriendo. Link relacionado: http://stackoverflow.com/questions/5849106/how-does-settimeout-works-on-node-js

    • Anónimo

      Muy buena aclaración!, lo que decis es que la función que dispara el setInterval() tarda mas que el intérvalo?, es verdad, puede traer graves problemas: puede estar ejecutandose la misma función mas de una vez al mismo tiempo … buen punto.
      Y por el clearTimeout(), la verdad que no lo conocía y es bastante útil.

      • Anónimo

        O sea si tenés código bloqueante, siempre va a ser bloqueante y no se va a hacer la ejecución en simultáneo.
        Acá escribí un ejemplo:

        function bloqueante(){
        var id_trucho = Math.floor(Math.random()*10000),
        now = Date.now();
        console.log('arranca el %s', id_trucho);
        for(var i = 0; i < 100000000; ++i);
        console.log('termina el %s en %s ms', id_trucho, Date.now() - now);
        }

        setInterval(bloqueante, 100);

        Este mismo código se retrasa tanto en cliente como en node o lo que sea