Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

Cómo hacer un sleep() en JavaScript: detener la ejecución durante un tiempo

Imagen ornamental por Lauren Kay (@lakael) en Unsplash, CC0Una función sleep() es una función que permite detener la ejecución del código durante un tiempo determinado. Usar una función similar a esta puede ser interesante para múltiples cuestiones: desde esperar a que se cumpla alguna condición antes de continuar con el código, hasta simular durante el desarrollo una conexión asíncrona que tarda mucho en devolver algo.

Casi todos los lenguajes y plataformas tienen algún modo de hacer esto, pero JavaScript, no dispone de esta funcionalidad de forma nativa. En este artículo vamos a ver cómo implementar una función sleep() en JavaScript usando dos métodos diferentes: el clásico y el moderno, explicando cómo lograrlo y cuáles son sus ventajas y desventajas.

sleep() con JavaScript "clásico" (ECMASCript 5)

En los tiempos de ECMAScript 5 (es decir, hasta antes de 2015) la única manera de simular una función sleep() era ejecutar un bucle a lo loco durante el tiempo que nos interesase "detener" la ejecución del código. La implementación de una función sleep() sería algo similar a esto:

var sleepES5 = function(ms){
    var esperarHasta = new Date().getTime() + ms;
    while(new Date().getTime() < esperarHasta) continue;
};

Como ves, lo que hace es sumar el número de milisegundos que se le pasen como parámetro y ejecutar un bucle sin hacer nada hasta que pase el tiempo estipulado. Más simple imposible.

Luego se podía meter en un fragmento de código para detenerlo durante un rato escribiendo algo así:

 function pruebaES5(){
    console.log('Inicio de la función de prueba.');
    sleepES5(3000);    //Dormimos la ejecución durante 3 segundos
    console.log('Fin de la función de prueba.');
};

Esto cumple con su cometido, y nos puede valer para hacer pruebas en desarrollo y cosas así, pero tiene varias pegas:

  • La ejecución realmente no se detiene, ya que lo que se hace es ejecutar un bucle miles de veces mientras esperamos. Por lo tanto se está utilizando CPU y, de hecho, si se pone un tiempo más o menos largo (unos cuantos segundos, dependiendo del navegador), acabaremos recibiendo un mensaje de aviso para detener la ejecución.
  • La interfaz de usuario se bloquea. Dado que JavaScript solo tiene un único hilo de ejecución, no puede ejecutar dos tareas a la vez (y el código no está en un Web Worker ni en un Service Worker), por lo que durante la espera la UI deja de responder. En realidad, los clics y acciones que lleves a cabo se encolan y se ejecutan cuando termina la ejecución del código "desbocado".

Si en nuestro ejemplo (que podrás descargarte al final) colocamos un botón que puedas pulsar durante la espera para hacer otra cosa, no hará caso hasta que la ejecución finalice.

En la siguiente figura se puede ver el resultado de ejecutar el código anterior (con los mensajes formateados para que se vean mejor), pulsando otro botón para hacer otra cosa durante el "sleep":

El resultado muestra que el código con el sleep() se ejecuta en el orden esperado, pero las pulsaciones del botón durante la espera se ejecutan después de ésta

Fíjate en que la ejecución se detiene 3 segundos, pero las pulsaciones del botón solamente se ejecutan al terminar la espera. Es decir, la espera no es tal, sino que es un bloqueo de la ejecución durante el tiempo indicado.

Es más, si mostramos el gestor de tareas del navegador y nos fijamos en el uso de CPU de la página durante el tiempo de espera, veremos que llega a picos muy altos de uso (que dependen de la potencia de tu máquina):

La imagen muestra el gestor de tareas de Chrome con un pico de CPU de mas del 70% durante la espera

Como digo, es una "ñapa" que funciona, pero que no es un sleep() de verdad y que presenta estos graves problemas. Pero, oye, en JavaScript "clásico", antes de ES6, era la única solución a nuestro alcance.

sleep() con navegadores modernos (ES6)

Una de las grandes y esperadísimas novedades cuando salió ECMASCript 6 era la incorporación al lenguaje de la característica de promesas (Promise).

Una promesa es un objeto que representa una tarea que "promete" ejecutarse en algún momento determinado (en el futuro o, incluso, en el pasado, aunque parezca un contrasentido), y son el mecanismo nativo de ECMAScript para ejecutar código asíncronamente.

Como las promesas tienen su complejidad, también se incorporaron al lenguaje las palabras clave async y await, para simplificar el manejo de funciones asíncronas con promesas.

Vamos a aprovechar esta funcionalidad para crear una función sleep() que de verdad funcione como esperamos.

Hacerlo, en realidad, es de lo más sencillo ya que tan solo necesitamos devolver una promesa y que ésta se resuelva automáticamente al cabo del tiempo indicado. O sea, el código es el siguiente:

var sleep = function(ms){
    return new Promise(resolve => setTimeout(resolve, ms));
};

Se crea la promesa y se indica como función de resolución un timeout que se ejecuta al cabo del tiempo indicado. La función del timeout tiene acceso al valor de la variable ms gracias a la clausura de la función.

¡Más fácil imposible!

Si no dominas las promesas, ni la programación asíncrona, ni las clausuras... te estás perdiendo mucho. Demasiado. Un desarrollador JavaScript que se precie debe dominar todo esto y muchas otras cuestiones avanzadas. Échale un vistazo a esta formación.

Vale, vamos a ver cómo se ejecutaría esto ahora en nuestro código:

 async function pruebaES6(){
    console.log('Inicio de la función de prueba.');
    await sleep(3000);    //Dormimos la ejecución durante 3 segundos
    console.log('Fin de la función de prueba.');
};

Fíjate en que la función que llame a nuestra función sleep(), como cualquier función que llame a un método asíncrono, debe llevar un modificador async delante. Y la llamada debe ir precedida de un await.

Durante las espera de 3 segundos hemos pulsado el botón de enviar mensajes por consola 3 veces, al igual que en el ejemplo anterior. El resultado en la consola es el siguiente:

El resultado muestra que el código con el sleep() se ejecuta en el orden esperado, y la UI no se bloquea, por lo que las pulsaciones del botón durante la espera se ejecutan en el momento adecuado

Si te fijas bien y lo comparas con la ejecución del código "tradicional" verás que, aparte de ejecutarse la espera, ahora la interfaz de usuario no se bloquea y cuando pulsamos el otro botón los mensajes llegan a la consola en el mismo momento en el que lo hacemos. Es decir, este sleep() sí que detiene de verdad la ejecución de nuestro código y además no bloquea el hilo principal del navegador y por lo tanto todo es interactivo y alejamos el fantasma del mensaje de "Un script está tardando mucho tiempo en ejecutarse".

Y ¿qué pasa con el uso de procesador?

Pues durante todo el tiempo en que se detienen la ejecución el uso de la CPU es del 0%:

Así que ¡perfecto! hemos conseguido un sleep() de verdad, que funciona como cabría esperar y sin desventajas por hacerlo.

Te dejo el código de prueba (1,22Kb) que he usado para este artículo para que puedas jugar con él.

¡Espero que te resulte útil!

José M. Alarcón Aguín Fundador de campusMVP, es ingeniero industrial y especialista en consultoría de empresa. Ha escrito diversos libros, habiendo publicado hasta la fecha cientos de artículos sobre informática e ingeniería en publicaciones especializadas. Microsoft lo ha reconocido como MVP (Most Valuable Professional) en desarrollo web desde el año 2004 hasta la actualidad. Puedes seguirlo en Twitter en @jm_alarcon o leer sus blog técnico o personal. Ver todos los posts de José M. Alarcón Aguín
Archivado en: Desarrollo Web

Boletín campusMVP.es

Solo cosas útiles. Una vez al mes.

🚀 Únete a miles de desarrolladores

DATE DE ALTA

x No me interesa | x Ya soy suscriptor

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.