Programación asíncrona con JavaScript

Javascript

En esta guía explicaremos en detalle qué es el asincronismo en JavaScript y cómo crear código asíncrono mediante callbacks, promesas y también usando async/await.

JavaScript es un lenguaje síncrono y monohilo por naturaleza, por lo que el código no puede crear nuevos hilos o ejecutarse en paralelo. Sin embargo, a pesar de que JavaScript no es un lenguaje multitarea, existen ciertos mecanismos que permiten la ejecución de código JavaScript asíncrono.

Qué es el asincronismo

Los ordenadores son asíncronos, o la menos su diseño así lo es. Cuando un evento es asíncrono significa que puede suceder al mismo tiempo que otros eventos, con independencia del flujo principal de la aplicación.

Los ordenadores ejecutan múltiples tareas de diferentes aplicaciones a la vez, por lo que reparten el tiempo de ejecución en varias ranuras temporales. Cuando se deja de procesar el código de un programa, continúa el de otro. Esto ocurre de forma cíclica y de forma transparente al usuario en todos los procesadores que contienen un único núcleo, de forma que pensemos que todas las aplicaciones se ejecutan a la vez. La excepción son los procesadores que contienen más de un núcleo, que son casi todos en la actualidad.

Muchos lenguajes de programación también son asíncronos por naturaleza, pudiendo ejecutar varias tareas a la vez, cuya ejecución se irá intercalando. Sin embargo, otros lenguajes son síncronos, como por ejemplo C, Java, Go, PHP, Ruby, Python o Swift, aunque suelen disponer de librerías que permiten crear peticiones asíncronas. Alguna librerías crearán nuevos hilos, mientras que otras crearán nuevos procesos.

Asincronismo en JavaScript

JavaScript es un lenguaje síncrono que se ejecuta en un único hilo, por lo que no será posible crear nuevos hilos que se ejecuten en paralelo. Es decir, que no podremos ejecutar más de una tarea a la vez. Por ejemplo, las asignaciones y operaciones del siguiente bloque de código se ejecutan secuencialmente, una tras otra:

const a = 1;
const b = 2;
const c = a + b;
console.log(c);

Sin embargo, JavaScript comenzó a utilizarse en los navegadores con el objetivo de dar respuesta a las acciones de los usuarios, ejecutando acciones cuando el usuario hace clic en un botón o pasa el ratón por encima de él.

Algunos ejemplos de estas acciones son los eventos onClick, onMouseOver y onChange entre muchos otros. Por ejemplo, cuando envías un formulario, también se ejecutará el evento onSubmit del mismo. Todos estos eventos no serían posibles en modelos puramente síncronos. Esto es debido a que los navegadores incluye APIs que permiten que JavaScript soporte estas funcionalidades.

Desde hace ya bastantes años, las webs son más interactivas y dinámicas. Entre otras cosas, surgió la necesidad de soportar operaciones que soportasen las peticiones de red externas para, por ejemplo, obtener los datos de una API localizada en otro servidor. Un ejemplo de ello sería una API REST. Por ello, dado que una petición externa lleva una cantidad indeterminada de tiempo, los navegadores incluyeron las funcionalidades asíncronas necesarias para soportarlas de forma asíncrona.

Si las APIs de un navegador fuesen síncronas, serían imposibles tareas triviales como hacer clic en un botón o hacer scroll. A esta imposibilidad se le denomina bloqueo. Las APIs a las que accede JavaScript se ejecutan de forma asíncrona, pudiendo funcionar en paralelo con las tareas que JavaScript ejecute secuencialmente De este modo, el usuario puede continuar usando el navegador mientras las tareas asíncronas son procesadas.

Lo mismo ocurre con el entorno de ejecución Node.js, que también introdujo métodos de entrada y salida asíncronos para tareas como el acceso a los ficheros o las peticiones de red. Cuando creas un endpoint en Node.js, sería una locura que la aplicación comprobase continuamente si existe una petición entrante. Por ello, el entorno de ejecución de Node.js dispone de una API que redirigirá las peticiones entrantes a las funciones correspondientes.

A diferencia de las APIs de un navegador, las APIs del entorno de ejecución de Node.js han sido creadas con C++.

Loop de eventos de JavaScript

A pesar de ser un lenguaje síncrono, JavaScript es capaz de gestionar peticiones asíncronas, aunque para explicar cómo, es necesario entender el loop de eventos y sus dos elementos principales; la pila o stack y la cola o queue.

El código JavaScript que no usa las APIs asíncronas del navegador se ejecutará secuencialmente tal y como podemos comprobar en la ejecución de las tres funciones que ves a continuación:

function f1() {
  console.log(1);
}

function f2() {
  console.log(2);
}

function f3() {
  console.log(3);
}

Si ejecutamos las funciones en el orden en el que han sido definidas, veremos que primero obtenemos el resultado de la función f1(), luego el de al función f2() y finalmente el de la función f3();

f1();
f2();
f3();

El resultado de la ejecución de las funciones anteriores será este:

1
2
3

Sin embargo, cuando se usa alguna API del navegador, las reglas cambian. Un ejemplo de ello es la función setTimeout(), que establecerá un temporizador e iniciará la acción definida tras el tiempo especificado. Debido a su naturaleza, la función setTimeout() necesita ser asíncrona, ya que de lo contrario no podríamos usar el navegador hasta que transcurriese el tiempo especificado en el temporizador.

Vamos a modificar la función f2() del ejemplo anterior y a agregar una llamada a la función setTimeout() para simular una petición asíncrona:

function f1() {
  console.log(1)
}

function f2() {
  setTimeout(() => {
    console.log(2)
  }, 0)
}

function f3() {
  console.log(3)
}

La función setTimeout() ejecutará la función que reciba como primer parámetro tras el tiempo especificado en milisegundos en su segundo parámetro. En este caso, usamos la función console.log() en el interior de la función anónima que hemos pasado como primer parámetro. La función se ejecutará tras 0 segundos.

A continuación vamos a ejecutar de nuevo las funciones:

f1(); 
f2(); 
f3();

Seguramente estés pensando que, dado que hemos establecido un retraso de 0 segundos para la ejecución del callback, el resultado sería el mismo que antes, mostrándose los números secuencialmente. Sin embargo, dado que la función se asíncrona, la función del setTimeout() se ejecutará al final:

1
3
2

Independientemente del tiempo que establezcas en la función setTimeout(), la función que le pases como parámetro se ejecutará al final, una vez completada la ejecución de las funciones síncronas. Esto es debido al modo al que el navegador gestiona la concurrencia o eventos paralelos. A este modelo se le llama loop de eventos.

Dado que JavaScript solamente puede ejecutar una sentencia al mismo tiempo, necesita proporcionar información al loop de eventos acerca de la sentencia a ejecutar. El loop de eventos agregará las sentencias a su cola de ejecución, aunque para entenderlo, debes entender cómo funciona la pila o call stack y la cola de mensajes de ejecución del loop de eventos.

Call Stack de JavaScript

La pila o call stack de JavaScript contiene el estado de la función que se está ejecutando actualmente. Una pila no es otra cosa que una especie de array que sigue la filosofía LIFO, que significa «Last In First Out». Por ello, solamente podrás agregar o eliminar elementos del final de la pila.

JavaScript ejecutará la llamada actual a una función de la pila, luego la eliminará y seguidamente pasará a ejecutar la siguiente llamada. Este es el motivo de que en el ejemplo propuesto, primero se finalizase la ejecución de la función actual entes de pasar a ejecutar la que hemos pasado como callback a la función setTimeout(), aunque el retraso fuese 0.

Este es el proceso que sigue el navegador al ejecutar las funciones síncronas del primer ejemplo:

  1. Primero se agrega la función f1() a la pila de llamadas o call stack, luego se ejecuta la función f1() y se muestra el resultado 1 por la consola, y finalmente se elimina la función f1() de la pila de llamadas.
  2. Seguidamente se agrega la función f2() a la pila y se ejecuta, mostrándose el resultado 2 por la consola, y finalmente se elimina la función f2() de la pila.
  3. A continuación se agrega la función f3() a la pila y se ejecuta, mostrándose el resultado 3 por la consola, y finalmente se elimina la función f3() de la pila.

Son embargo, el navegador seguirá este otro proceso al ejecuta las funciones del segundo ejemplo, cuya función f2() contiene el setTimeout():

  1. Primero se agrega la función f1() a la pila y se ejecuta, mostrándose el resultado 1 por la consola, y finalmente se elimina la función f1() de la pila.
  2. Seguidamente se agrega la función f2() a la pila y se ejecuta.
  3. Ahora, se agrega el setTimeout() a la pila de llamadas y se ejecuta la API correspondiente a la función setTimeout(), que inicia un temporizador y agrega la función anónima definida como primer parámetro de la misma a la cola. Finalmente se elimina la función setTimeout() de la pila.
  4. Luego se agrega la función f3() a la pila y se ejecuta, mostrándose el resultado 3 por la consola. Finalmente se elimina la función f3() de la pila.
  5. Ahora, el loop de eventos comprobará si existe algún mensaje pendiente en la cola, momento en el que encuentra la función anónima agregada por el setTimeout(). Esta función se agrega a la pila y al ejecuta, mostrándose el resultado 2 por la consola. Finalmente se elimina la función anónima de la pila.

Ahora quizás tengas ya más claro el motivo del resultado del segundo grupo de funciones. Tal y como ves, se ha introducido el concepto de cola, que explicaremos a continuación.

Cola de mensajes de JavaScript

Las cola de mensajes o cola de tareas es en donde las funciones esperan a ser ejecutadas. El primer mensaje en entrar en la cola, será el primero en salir, siguiendo la filosofía «First In First Out».

Cuando la pila o call stack está vacía, el loop de eventos comprobará la cola para ver si existen mensajes en espera, comenzando por el que más tiempo lleva en la cola. Cuando se encuentre un mensaje, se agregará a la pila, que ejecutará la función asociada al mensaje.

En nuestro segundo ejemplo, en el que usamos la función con el setTimeout(), la función anónima se ejecuta tras finalizar la ejecución de las funciones de primer nivel, ya que el temporizador es de 0 segundos. Que el temporizador esté a 0 no implicará que la función anónima se vaya a ejecutar en exactamente 0 segundos. Lo mismos ocurriría si hubiésemos establecido el valor del temporizador en tres segundos o cualquier otro valor. El tiempo indicado es el tiempo que transcurrirá hasta que la función se agregue a la cola.

Si una vez transcurrido el tiempo la función se agregase directamente a la pila en vez de a la cola, la función que se estuviese ejecutando se interrumpiría, provocando resultados imprevistos.

Existe también otra cola en JavaScript, que es la cola de tareas o «job queue», usada para gestionar las promesas, que veremos a continuación. Las tareas de esta cola tienen prioridad sobre otras tareas que se agregan a la cola de mensajes, como es el caso de la función setTimeout().

Programación asíncrona con JavaScript

En nuestro ejemplo ejemplo anterior, la función setTimeout() se ejecuta después de que el contexto de ejecución de nivel superior finalice su ejecución. Sin embargo, en caso de que quisieras que la tercera función f1() se ejecutase después del timeout, tendrías que usar alguna técnica de programación asíncrona. En lugar de un timeout podríamos estar hablando de una petición API a otro servidor que devuelve ciertos datos, en cuyo caso nos interesaría crear una función asíncrona capaz de ejecutar las acciones deseadas cuando recibamos una respuesta.

En JavaScript existen diversos métodos mediante los cuales puedes ejecutar código de forma asíncrona. Puedes usar callbacks con las APIs del navegador o del entorno de ejecución, puedes usar promesas o también puedes usar async/await.

Programación asíncrona con Callbacks

Vamos a ver cómo puedes ejecutar código de forma asíncrona mediante callbacks. Un callback es una función pasada como argumento a otra función, tal y como explico en el siguiente tutorial:

La función que recibe el callback como argumento recibe el nombre de función de orden superior. Un callback no es ninguna función especial ni dispone de una sintaxis diferente. De hecho, puedes usar cualquier función como callback. Las funciones callback tampoco son funciones asíncronas por naturaleza. Sin embargo, esta técnica nos permite crear funciones asíncronas.

Vamos a ver cómo crear crear y ejecutar una función callback. A continuación puedes ver cómo ejecutamos un callback en una función de nivel superior:

// Una función
function funcion() {
  console.log('Una función');
}

// Función de orden superior
function funcionOrdenSuperior(callback) {
  // Se produce el callback
  callback();
}

// Pasamos la función como parámetro
funcionOrdenSuperior(funcion);

En el código anterior hemos definido una función funcion y otra función llamada funcionOrdenSuperior que acepta una función callback como parámetro, que además es ejecutada en su interior. Luego pasamos la función funcion a la función funcionOrdenSuperior. Si ejecutamos el código anterior verás la siguiente salida:

Una función

Volvamos ahora al ejemplo de las tres funciones propuesto anteriormente. Lo que queremos es que la función f3 se ejecute una vez la función f2 haya completado su ejecución. Para ello podemos valernos de un callback. En lugar de ejecuar las funciones f1, f2 y f3 como en el contexto de ejecución superior, vamos a pasar la función f3 como argumento a la función f2.

Nuestro objetivo es que la función f2 ejecute el callback después de que el código asíncrono se haya ejecutado. Para ello vamos a modificar el código anterior:

function f1() {
  console.log(1)
}

function f2(callback) {
  setTimeout(() => {
    console.log(2);
    callback();
  }, 1000);
}

function f3() {
  console.log(3)
}

A continuación vamos a ejecutar las dos primeras funciones, pasando la tercera función a la segunda como argumento:

f1();
f2(f3);

Este sería el resultado de la ejecución de las funciones:

1
2
3

Primero se muestra por la consola el número 1 de la primera función. Luego, tras finalizar el temporizador del timeout de la segunda función, que es de un segundo, se mostrará el número 2. Finalmente se ejecuta al función f3() en el interior del timeout, que mostrará el número 3.

Y con esto ya hemos retrasado la ejecución de la función hasta que se complete un evento de una API del navegagor. Una petición API a un servidor externo funciona del mismo modo, ya que en el fondo se trata de ejecutar código cuando se cumplen una o más condiciones.

Las funciones callback no son asíncronas, pero la función setTimeout() es una API web asíncrona responsable de ejecutar tareas de forma asíncrona. Los callback permiten gestionar el resultado de las tareas asíncronas, pudiendo así actutar en consecuencia, ejecutando ciertas tareas si la tarea se ha completado con éxito o mostrando un error en caso contrario.

Antes de continuar, en caso de que quieras saber más cosas acerca de los callbacks, consulta el siguiente tutorial:

Sin embargo, has de saber que las funciones callback no están exentas de problemas, ya que es muy fácil alcanzar el temido callback hell, que es el mayor problema de los callbacks.

Cómo evitar el callback hell

Las funciones callback son un método efectivo a la hora de lidiar con la ejecución de las funciones hasta que se hayan completado ciertas tareas. Sin embargo, si anidamos varios callbacks el código puede llegar a ser un desastre incomprensible e ilegible.

A este problema también se le suele denominar pyramid of doom. A continuación puedes ver un ejemplo en el que anidamos varios callbacks:

function callbakHell() {
  setTimeout(() => {
    console.log('a');
    setTimeout(() => {
      console.log('b');
      setTimeout(() => {
        console.log('c');
      }, 1000);
    }, 2000);
  }, 3000);
}

Cada una de las funciones setTimeOut() del código anterior se ejecuta en el contexto de una función de nivel superior, agregando un nivel adicional de profundidad al contexto con cada callback.

El código anterior mostraría lo siguiente por pantalla.

a
b
c

Si en lugar de los console.log() ejecutásemos código más complejo, la cosa se complicaría bastante. Además, suele ser necesario usar algún método para gestionar los errores de las peticiones. La cosa puede complicarse todavía más cuando agregas gestión de errores al código asíncrono y envías los datos de cada una de las respuestas a la siguiente petición. Esto resultará en un código difícil de leer y mantener. En el siguiente ejemplo creamos otro callback hell, así que no te molestes demasiado en entenderlo, sino en fijarte en lo enrevesado que resulta:

// Ejemplo de una función asíncrona
function peticionAsincrona(args, callback) {
  // Lanzar un error si no hay argumentos
  if (!args) {
    return callback(new Error('Algo ha ido mal.'));
  } else {
    return setTimeout(
      // Agregamos un valor aleatorio para que parezca que la función asíncrona devuelve diferentes datos
      () => callback(null, { body: args + ' ' + Math.floor(Math.random() * 10) }),
      500
    );
  }
}

// Peticiones asíncronas anidadas
function callbackHell() {
  peticionAsincrona('Primera', function primera(error, response) {
    if (error) {
      console.log(error);
      return;
    }
    console.log(response.body);
    peticionAsincrona('Segunda', function segunda(error, response) {
      if (error) {
        console.log(error);
        return;
      }
      console.log(response.body);
      peticionAsincrona(null, function tercera(error, response) {
        if (error) {
          console.log(error);
          return;
        }
        console.log(response.body);
      })
    })
  })
}

// Ejecución
callbackHell();

En el código anterior debemos tener en cuenta tanto las posibles respuestas válidas como los posibles errores, provocando que la función callbackHell() resulte muy confusa.

Vamos a ejecutar la función anterior:

callbackHell();

Tal y como puedes ver, obtendrás un error, agregado a propósito para que puedas ver lo difícil que resulta identificar en dónde ha ocurrido:

Primera 0
Segunda 7
Error: Algo ha ido mal.
    at peticionAsincrona (<anonymous>:4:21)
    at segunda (<anonymous>:28:7)
    at <anonymous>:8:13

Los callbacks son ideales para pequeñas tareas asíncronas, pero su uso resulta confuso cuando tenemos que anidar varias funciones asíncronas. Para solucionar este problema se introdujeron las promesas en JavaScript ES6.

Programación asíncrona con Promesas

Las promesas son actualmente el elemento fundamental mediante el cual se gestiona el código asíncrono en JavaScript. Una promesa es un objeto que contiene un estado y un valor. Estos se almacenan en las propiedades PromiseStatus y PromiseValue respectivamente.

Las promesas son por naturaleza asíncronas, por lo que pueden o no haber sido resueltas. Por ello, el estado de una promesa puede tener los valores pending, fulfilled o rejected. Tendrá el estado pending cuando la promesa todavía no haya sido resulta, el estado fulfilled cuando haya sido resuelta con éxito y el estado rejectec vuando haya ocurrido algún error durante su ejecución.

Una promesa recibirá una función como parámetro, que será la función asíncrona que queremos ejecutar. Además, dicha función, recibe dos parámetros; el primero es una función que se ejecutará cuando la promesa se resuelva con éxito, mientras que el segundo es una función que se ejecutará cuando la promesa fracase o sea rechazada.

const promesa = new Promise((resolve, reject) => {
  const numA = Math.floor((Math.random() * 10) + 1);
  const numB = Math.floor((Math.random() * 10) + 1);
  
  if (numA >= numB) {
    setTimeout(() => resolve('numA mayor o igual que numB'), 1000);
  } else {
    reject('numA menor que numB');
  }
});

El estado de una promesa pasará a tener el valor fulfilled cuando ejecutemos la función resolve. Del mismo modo, pasará a tener el valor rejected cuando ejecutemos la función reject. Estas funciones que pasamos como argumentos pueden llamarse de cualquier otra forma, aunque lo más habitual es nombrarlas como resolve y reject, aunque a la primera también se le suele llamar sencillamente rest.

En el siguiente ejemplo vamos a crear una función que cargue un archivo de imagen desde el sistema, cuya ruta le pasamos como parámetro. Para ello tendremos que ejecutar ciertas funciones asíncronas, por lo que puede ser conveniente devolver una promesa como respuesta:

function cargarImagen(imagen) {
  return new Promise( (res, res) => {
    try {
      const imagen = readFile(imagen);
      resolve(imagen);
    } catch(error) {
      reject(new Error(error));
    }
  }):
}

Ahora vamos a ejecutar la función anterior:

const resultado = cargarImagen('./img/foto.png');

Si ahora inspeccionas la constante en la que hemos almacenado el resultado de la promesa ejecutando el método console.log(resultado), podrás ver el valor de las propiedades PromiseStatus y PromiseValue:

__proto__: Promise
[[PromiseStatus]]: "fulfilled"
[[PromiseValue]]: { data: { ... } }

Sin embargo, seguramente quieras obtener el resultado en sí de la promesa. Para ello puedes usar los métodos then(), que se ejecutará cuando la promesa sea resulta, catch(), que se ejecutará cuando la promesa sea rechazada o finally(), que se ejecutará en todo caso, independientemente de si la promesa se ha resuelto o ha sido rechazada:

cargarImagen('./img/foto.png')
.then(imagen => console.log(imagen))
.catch(error => console.log(error))
.finally(() => console.log('Terminado'));

El método then() recibirá como parámetro el valor con el que resolvamos la promesa. Mientras que el método catch() recibirá como parámetro el valor con el que rechacemos la promesa.

Además, es posible encadenar múltiples llamadas al método then(), ya que el resultado de then() es en sí una promesa aunque explícitamente no estés devolviendo una:

Promise.resolve(2)
.then(res => res * 2) // 4
.then(res => res * 2) // 8
.then(res => res * 2) // 16 

Del mismo modo, también podríamos obtener una imagen del sistema y realizar varias tareas asíncronas adicionales, como comprimir la imagen, agregarle algún filtro y guardarla en el sistema:

cargarImagen('./img/foto.png')
.then(imagen => comprimirImagen(imagen))
.then(imagenComprimida => filtrarImagen(imagenComprimida))
.then(imagenFiltrada => guardarImagen(imagenFiltrada))
.then(res => console.log('Imagen guardada correctamente!'))
.catch(error => throw new Error(error));

Tal y como ves, el uso de promesas mejora mucho la legibilidad del código en comparación con el uso de callbacks, evitando así el callback hell. Sin embargo, las promesas todavía resultan demasiado complejas, siendo este el motivo de que en la versión ES7 de JavaScript se introdujese la sintaxis async/await.

Ejecución asíncrona con async/await

Mediante la sintaxis async/await podrás crear y ejecutar funciones asíncronas de un modo más sencillo que con las promesas. Pero no nos engañemos, las que esta sintaxis no es otra cosa que una abstracción de las promesas.

La gran diferencia del uso de  async/await  en comparación con las promesas radica en que ahora podrás devolver directamente un valor. Para crear una función asíncrona mediante esta sintaxis bastará con que antepongas el término async a una función o método:

async funcion saludar() {
  return('Hola!');
}

A pesar de que las funciones asíncronas siguen devolviendo una promesa, la ventaja está en que ahora puedes usar el término await para devolver directamente el resultado de una promesa:

const saludo = await saludar();

Anteponiendo await a una promesa, que es lo que devuelve la función saludar(), se esperará a que esta devuelva un resultado. Si muestras por la consola el resultado de la constante saludo, podrás comprobar que contiene el resultado de la promesa y no la promesa en sí misma:

console.log(saludo);  // Hola!

Sin embargo, solamente podrás usar await en el interior de una función asíncrona. Además, debes saber que si no ejecutas una función asíncrona usando await, el código que definas tras la función no esperará por los await que definas dicha función.

En ocasiones puede resultar confuso el uso de async/await, especialmente cuando se usa await en el interior de una función asíncrona invocada sin usar await, ya que puede dar la sensación de que await no esperar realmente al resultado de una función asíncrona, cuando no es así. Vamos a explicar este problema con un ejemplo.

const miFuncion= () => Promise.resolve('C');

async function  miFuncion() {
  console.log('B');
  const res = await getC();
  console.log(res);
}

console.log('A')
miFuncion();
console.log('D');

En el ejemplo anterior, primero se mostrará A por la consola, luego B, seguidamente D y finalmente C. Esto es debido a que la función miFuncion() devuelve una promesa a la que no hemos invocado con await. Veamos en detalle lo que ocurre:

  1. Primero, el motor de JavaScript ejecuta el contexto global, encontrándose con el primero console.log(), que será agregado a la pila. Durante su ejecución, se mostrará A por la consola y finalmente se eliminará de la pila.
  2. Luego se ejecuta la función asíncrona miFuncion(), ejecutándose el console.log de su interior,  que se agregará a la pila y se ejecutará, mostrándose B por la consola. Seguidamente se eliminará de la pila.
  3. Seguidamente, continuando con la ejecución de la función miFuncion(), llegamos a la función asíncrona getC() ejecutada con await. Lo primero que ocurre es que el valor por el que se espera con await es devuelto por la función getC(). Para que esto ocurra, JavaScript agrega la función getC() a la pila, que se ejecutará, devolviendo una promesa. Si en el interior de la función getC() se mostrase algo por la consola, se mostraría de inmediato, pero no es el caso. Una vez se haya resuelto la promesa y se haya devuelto el valor C, el motor de JavaScript se encontrará con el término await y frenará la ejecución de al función miFuncion(), que se suspenderá. El resto de la función miFuncion() se ejecutará más adelante como una microtarea.
  4. Entonces, el motor de JavaScript regresa al contexto de ejecución principal en el que se llamó a la función asíncrona, mostrándose D por la consola.
  5. Finalmente, una vez ejecutado el contexto principal, el loop de eventos comprobará si existen tareas en la cola y, dado que no existen, comprobará si existen microtareas. Entonces, se encontrará con la función miFuncion() en la cola, la agregará a la pila y continuará su ejecución en donde se había pausado. Entonces, la constante res recibirá finalmente un valor, que será mostrado por la consola en el siguiente console.log, mostrándose C por la consola.

De haber invocado a la función miFuncion() con un await, en el caso de estar en el interior de una función asíncrona, el resultado sería otro, ya que primero se mostraría A por la consola, luego B, seguidamente C y finalmente D.

Para obtener más información acerca del uso de async/await, consulta el siguiente tutorial:

Y esto ha sido todo.


Avatar de Edu Lazaro

Edu Lázaro: Ingeniero técnico en informática, actualmente trabajo como desarrollador web y programador de videojuegos.

👋 Hola! Soy Edu, me encanta crear cosas y he redactado esta guía. Si te ha resultado útil, el mayor favor que me podrías hacer es el de compatirla en Twitter 😊

Si quieres conocer mis proyectos, sígueme en Twitter.

1 comentario en “Programación asíncrona con JavaScript

Deja una respuesta

“- Hey, Doc. No tenemos suficiente carretera para ir a 140/h km. - ¿Carretera? A donde vamos, no necesitaremos carreteras.”