> Manuales > Manual de JavaScript

Qué es Javascript asíncrono. Por qué es tan importante la asincronía en Javascript. Cómo es el código asíncrono y cuáles son los mecanismos para poder controlar los procesos asíncronos.

Javascript asíncrono

Este va a ser un artículo de cultura general sobre Javascript y su modelo de trabajo asíncrono, algo esencial para dominar el lenguaje de programación por excelencia para la web. Veremos qué es la asincronía y esperamos ofrecer explicaciones claras y sencillas para que puedas entender las bases y despejar dudas.

Para explicar todos los detalles sobre Javascript asíncrono vamos a tratar los siguientes apartados de interés.

Qué es síncrono

Antes de estudiar qué es la asincronía vamos a entender qué significa síncrono, lo que sería el concepto contrario. Es útil empezar por aquí porque nuestro modelo de pensamiento a la hora de programar es principalmente síncrono. Tener claro este concepto nos ayudará a entender mejor su opuesto.

Algo síncrono según la RAE es algo que coincide en el tiempo. Desde nuestro punto de vista como programadores en Javascript nos aclara algunas cosas, pero debemos dejar claros algunos matices adicionales.

Como sabes, al escribir programas tenemos una serie de sentencias de código para definir los procesos o algoritmos. Síncrono quiere decir que esas sentencias se ejecutarán una detrás de otra, de manera continua en el tiempo, sin que existan pausas entre la ejecución de una y otra sentencia.

Esta es la manera común que tenemos de entender los programas y la manera como hemos aprendido generalmente un lenguaje de programación.

let x = 1;
x++;
console.log(x);

Todas estas sentencias se ejecutarán una detrás de otra, de manera prácticamente instantánea, pues el hilo de procesamiento de Javascript es muy rápido, pero siempre respetando el orden con el que se han escrito en el código. De modo que, al acabar de ejecutarse, aparecerá un mensaje en la consola con el valor de x, tal como ha quedado después de las tres sentencias.

Qué es asíncrono

Por contra, asíncrono significa que no se tiene por qué respetar el orden de las sentencias durante su ejecución en el tiempo. Es decir, pueden aparecer en el código fuente en un orden y sin embargo se pueden llegar a ejecutar en otro orden distinto.

Esto quizás puede resultar un poco extraño, porque generalmente en programación tendemos a escribir algoritmos en los cuales se van realizando distintas cosas, paso a paso, para la consecución de un objetivo. Si las sentencias no se ejecutan en el orden en el que las hemos escrito lo normal sería pensar que los programas no producirán los resultados deseados. Sin embargo, en la práctica y en ciertas situaciones, la asincronía de Javascript puede requerir que nosotros escribamos las sentencias de los programas en orden distinto a como se van a ejecutar.

El ejemplo más sencillo de asincronía lo encontramos con la función setTimeout() de Javascript, que puede retrasar la ejecución de una función suministrada hasta pasados unos milisegundos.

Entendiendo setTimeout

Por si acaso alguien no conoce la función setTimeout() vamos a ver un rápido ejemplo:

setTimeout( function() {
  console.log('pasaron 2 segundos');
}, 2000);

En la invocación de la función setTimeout() suministramos dos argumentos.

Por lo tanto, en el código anterior veremos que el mensaje del console.log() se mostrará pasados dos segundos (2000 milisegundos).

Tienes decenas de ejemplos de setTimeout() a lo largo de todo desarrolloweb, puedes usar el buscador para encontrar mucha más información.

Ejemplos de código asíncrono con setTimeout

Ahora vamos a ver este código donde tenemos un ejemplo de asincronía, donde podemos comenzar a experimentar con esta característica de Javascript:

let x = 1;
setTimeout( function() {
  x++;
}, 1);
console.log(x);

En este ejemplo, que es muy similar al que hemos visto en el primer código expuesto en este artículo, por lo que imagino que lo entenderás. Tenemos asincronía por el hecho de usar la función setTimeout(). Por tanto, ¿Qué piensas que va a producirse de salida por consola al ejecutar el programa?

En realidad por consola nos aparecerá el valor 1, porque el incremento de x se realizará más tarde de mostrarse en consola, aunque sea solamente 1 milisegundo más tarde.

Realmente es muy fácil interpretar este código si llegaste a entender la función setTimeout(). Podríamos acudir a ejemplos un poco más complejos.

let x = 1;
setTimeout( function() {
  x = 3;
  console.log(x);
  x = 20;
}, 100);
setTimeout( function() {
  x +=10;
  console.log(x);
}, 10);
setTimeout( function() {
  console.log(x);
}, 1);

La salida de este programa será el valor 1, 11, 3. Tampoco tiene mucho misterio pero para deducirlo tienes que ir viendo el código por partes, mirando los milisegundos de cada setTimeout() y viendo el valor que tendrá la variable x en cada paso.

Por qué lo asíncrono es tan importante en Javascript

Con Javascript nos encontramos ante un lenguaje que, desde su construcción, aporta unas características muy relevantes y relacionadas con la asincronía. Básicamente todo parte del hecho de que en Javascript tenemos únicamente un único hilo de ejecución de los programas.

Hay lenguajes que, como PHP, son capaces de levantar diversos procesos para mantener hilos de ejecución paralelos. Por ejemplo, cada vez que un usuario consulta una página se levanta un proceso de ejecución de PHP para procesar la página, componer el HTML y devolverlo al usuario. Eso lo hace el servidor web sin que nosotros tengamos que programar nada, de modo que en la práctica el servidor podrá tener varios procesos de PHP ejecutándose al mismo tiempo, donde cada uno de ellos prepara el HTML que debería enviar a cada cliente que se conecta con la web.

Javascript, sin embargo, solamente tiene un único proceso funcionando, lo que en principio podría parecer una desventaja, pero en la práctica su gestión asíncrona hace que no sea un inconveniente. Incluso a veces puede significar una mejora significativa en el rendimiento de las aplicaciones!

Comportamiento bloqueante del modelo síncrono

En lenguajes síncronos como PHP generalmente tenemos un comportamiento bloqueante. Vamos a entender qué significa esto con un ejemplo.

Cuando accedemos con PHP a una base de datos tendremos que realizar una consulta contra el sistema gestor de la base de datos. Este servidor tardará en responder, aunque sea unos milisegundos. Durante ese tiempo PHP estará esperando simplemente la respuesta del servidor de la base de datos y mientras que eso ocurre el hilo de ejecución del programa simplemente estará esperando ocioso. Dicho de otro modo, el proceso de ejecución del código estará bloqueado en esa sentencia y no hará nada (no ejecutará nada más) hasta que el sistema gestor de la base de datos le responda con el resultado de la operación solicitada.

En PHP estas esperas no impactan negativamente porque cada proceso de ejecución estará atendiendo a un usuario y no importan tanto que se tenga que esperar a la base de datos u otros trabajos que pueden hacer que el programa sea más lento. Sin embargo, si PHP tuviera un único hilo de ejecución para atender a todos los visitantes que pueda tener una web ocurriría que las esperas en las consultas a la base de datos bloquearán a todos los usuarios, porque hasta que no se reciban los resultados de una consulta para atender a un usuario no podrían procesarse las páginas solicitadas por otros usuarios de la web.

Lenguajes bloqueantes vs lenguajes no bloqueantes

Como hemos explicado lenguajes como PHP son bloqueantes. Es decir, que al realizar procesos pesados, que requieran un tiempo en ejecutarse (acceso a una base de datos, al sistema de archivos, a un API, etc) el proceso de ejecución se queda en estado bloqueado, esperando que ese proceso termine antes de continuar con la siguiente línea de código.

Sin embargo, existen los lenguajes no bloqueantes, como Javascript, que tienen otro comportamiento. En ellos, el tiempo en el que se espera a los procesos lentos, o no inmediatos, que a menudo no dependen del propio lenguaje sino de la respuesta de un sistema distinto (como el motor de la base de datos), el lenguaje libera el hilo de ejecución, de modo que es capaz de atender otras cuestiones.

Gracias a su característica no bloqueante Javascript puede atender a muchas cosas a la vez, aunque solo tenga un hilo de ejecución. Simplemente porque cualquier cosa que necesite hacer que no sea inmediata, libera el hilo de ejecución para que otras cosas se puedan ir realizando al mismo tiempo.

Aquí está la clave de Javascript y por qué consigue funcionar de manera fluida, incluso en máquinas con poca capacidad de procesamiento. Simplemente tiene un único proceso, por lo que no exige demasiado. Si hay cosas que no dependen de Javascript, no congela su funcionamiento y puede dedicarse a otras cosas.

Sin embargo, como programadores, para poder componer el código de aplicaciones en lenguajes no bloqueantes, debemos entender muy bien la asincronía y acostumbrarnos a escribir el código de las aplicaciones de una manera diferente, a menudo más compleja de lo que nos requieren los lenguajes bloqueantes.

Tendrás que gestionar la asincronía de Javascript para realizar tareas tan comunes como el acceso a las bases de datos, o el acceso al sistema de archivos en el lenguaje NodeJS o para realizar consultas a servicios web o APIs desde el navegador con Ajax, por poner algunos ejemplos.

Aunque Javascript sea un buen lenguaje para aprender a programar, porque tiene cantidad de aplicaciones directas, temas como la asincronía lo hacen más complejo. Muchas veces se dejan pasar en los tutoriales o cursos básicos, pero son importantes de dominar para poder sacarle partido en situaciones diversas.

Mecanismos para la gestión de los procesos asíncronos

Ahora vamos a comentar brevemente los distintos mecanismos que nos ofrece Javascript para gestionar los procesos asíncronos, desde los más sencillos hasta los de más alto nivel.

Funciones callback

Las funciones callback son funciones que se suministran a los procesos asíncronos, que contienen el código que debe ejecutarse una vez que acaban. Las funciones callback se envían como argumento a las funciones que realizan procesos asíncronos y el propio motor de Javascript se encargará de procesar esos callbacks cuando corresponda.

La función setTimeout() que hemos visto antes tenía una función callback, que es aquella que debe procesarse pasados un número de milisegundos. A menudo expresamos las funciones callbacks mediante funciones anónimas.

setTimeout( function() {
  console.log('Se está ejecutando la función callback');
}, 1000);

Si te interesa, en este artículo puedes profundizar un poco más sobre las funciones callback de Javascript.

Promesas

Las promesas son herramientas un poco más avanzadas para organizar el código de las funciones asíncronas. Nos permiten de manera centralizada especificar qué queremos hacer cuando un proceso asíncrono se ha ejecutado correctamente y lo que se debería hacer cuando el proceso asíncrono ha resultado en una situación de error.

Las promesas puedes verlas como objetos que te devuelven los procesos asíncronos. Cuando un proceso asíncrono funciona devolviendo promesas, en lugar de alimentarlo con una función callback usaremos la promesa de retorno para definir qué queremos hacer en cada caso.

El flujo principal del código definirá qué es lo que se debe hacer cuando la promesa se resuelva, mediante los métodos then() y catch().

Aquí podemos ver un código que usa el API Fetch para la realización de una solicitud Ajax. Fetch tiene la característica de funcionar devolviendo promesas.

fetch('https://jsonplaceholder.typicode.com/photos')
  .then(res => console.log(res))
  .catch(err => console.log(err));

Por supuesto, sabemos que para entender este código hacen falta las debidas explicaciones. Por eso, si te interesa, en este artículo puedes profundizar sobre las promesas en Javascript.

Async Await

Es el último modelo para gestionar los procesos asíncronos (por lo menos el último modelo de gestión asíncrona que ha aportado Javascript). Este modelo en realidad es lo que conocemos como un "azúcar sintáctico" que nos permite resolver los procesos asíncronos con un estilo de programación similar al que usaríamos en la programación síncrona.

Por tanto, las personas comúnmente preferirán implementar comportamientos usando Async / Await porque el código resultante resultará más claro en la mayoría de las ocasiones.

Para usar Async Await simplemente debemos marcar una función como asíncrona (con async) y entonces en ella podemos usar la palabra "await" cada vez que necesitemos esperar que se resuelva un proceso asíncrono.

Aquí te dejamos un ejemplo de cómo se usa Async / Await en una función que hace uso de fetch, que es un proceso asíncrono para obtener datos por Ajax. La función fetch en realidad trabaja con promesas, pero gracias a Async / Await nos podemos ahorrar tratarlas con el bloque "then".

async function funcionAsincrona() {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos/6')
  const json = await response.json()
  console.log(json);
}
funcionAsincrona();

No te preocupes si todavia no entiendes Async / Await. Tenemos un artículo para explicarlo con todo detalle: async await en Javascript.

Event loop de Javascript

Para acabar este artículo vamos a tocar otro tema interesante relacionado con la gestión de la asincronía realizada internamente por el lenguaje Javascript. Se trata del denominado Event Loop, que es quien atiende todos los procesos asíncronos, registra callbacks y las ejecuta cuando se necesita.

Este es un concepto que vemos con más detalle en el Manual de NodeJS, ya que afecta mucho al tipo de programas que se hacen en esta plataforma.

El Event Loop no es más que un bucle que Javascript implementa de manera transparente para el desarrollador, que está siempre ejecutándose y atento a cualquier demanda de ejecución.

Aquí tenemos una imagen simplificada que se usa para explicar el event loop.

Javascript asíncrono

Simplemente queremos que notes en el diagrama que el event loop se está ejecutando en un ciclo infinito. Este es el hilo único de ejecución de Javascript, que se encarga de hacer todas las operaciones demandas en el código de las aplicaciones.

Cuando una operación depende de un agente externo, como el acceso a la base de datos, simplemente se guarda la función callback, que debe ejecutarse al terminar. Este proceso puede durar un tiempo indeterminado, durante el cual el event loop se ocupa de hacer cualquier otra cosa, como por ejemplo atender otro request de otro usuario, o atender la interacción del usuario con la página, si está en un entorno de cliente.

Finalmente, cuando el proceso termina, el callback configurado para ejecución a su fin se procesa.

Conclusión sobre la asincronía en Javascript

Esperamos que este artículo te haya aclarado algunas ideas sobre cómo es el lenguaje Javascript y cómo funciona. Es verdad que no hemos ahondado mucho en el código y te queda pendiente profundizar un poco más sobre los distintos modos de procesar la asincronía, pero el objetivo del artículo era ofrecer algo de cultura general sobre Javascript. Espero que te haya resultado interesante.

Ahora, siguiendo los enlaces del propio artículo podrás encontrar mucha más información práctica.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual