imagen

Ciclos async y sync en javascript

Como sabemos, javascript por definición es asíncrono, esto significa que podemos llamar a una función y sin esperar que finalice, continuar con la siguiente instrucción, a esto le llamamos "ASYNC" o "no bloqueante" pero también podemos hacer que la ejecución de nuestro código "espere" a que esta finalice una función para poder continuar con la siguiente instrucción, a esto le llamamos "SYNC" o "bloqueante".

El término asíncrono se refiere al concepto de que más de una cosa ocurre al mismo tiempo o en paralelo, o múltiples cosas relacionadas ocurren sin esperar a que la previa se haya completado.
Ref

El término síncrono se refiere al concepto de que las cosas ocurren de manera secuencial o lineal, una después de la anterior, las cosas relacionadas deben esperar a que la previa se haya completado.

Dicho lo anterior, nuestros LOOPS de toda la vida pueden tener comportamiento SYNC o ASYNC según el caso de uso que estemos resolviendo.

Vamos a ver ambos ejemplos y cómo abordarlos

Entorno

Para el ejemplo nos ayudaremos de dos funciones dummy que simularan operaciones cualquiera.

  • dummyTask Una función de ejemplo que tardara 3 segundos en ejecutarse, simulando una operación/función cualquiera
  • dummyFunctionAsync Función que internamente llama 3 veces a dummyTask de forma lineal (no async) que tardara 9 segundos en terminar su ejecución (3 dummyTask de 3seg cada una), retornando una promesa después de finalizadas las tres llamadas.
const dummyTask = (taskNumber) => {
  //Does many things
  return new Promise((resolve, reject) =>
    setTimeout(() => {
      console.log(`\t\tTask ${taskNumber} finished after 3 second.`);
      resolve(true);
    }, 3000)
  );
};

const dummyFunctionAsync = async (iterationNumber) => {
  console.log(`\tdummyFunctionAsync called ${iterationNumber} started`);
  //Does many things
  await dummyTask(1);
  await dummyTask(2);
  await dummyTask(3);
  console.log(`\tdummyFunctionAsync called ${iterationNumber} finished\n`);
};

Ciclo SYNC (Lineal)

Las llamadas se ejecutan 1 detrás de la otra, esperando siempre que finalice una función antes de continuar con la siguiente.

const items = [1, 2, 3];

// Ejecución en linea de las llamadas a dummyFunctionAsync
const mainFunctionWithForOf = async () => {
  console.log("forOf: started");
  let index = 0;
  for (const item of items) {
    await dummyFunctionAsync(index);
    index++;
  }
  console.log("forOf: ending");
};

Ejemplo de la salida en consola

forOf: started
        dummyFunctionAsync called 0 started
                Task 1 finished after 3 second.
                Task 2 finished after 3 second.
                Task 3 finished after 3 second.
        dummyFunctionAsync called 0 finished

        dummyFunctionAsync called 1 started
                Task 1 finished after 3 second.
                Task 2 finished after 3 second.
                Task 3 finished after 3 second.
        dummyFunctionAsync called 1 finished

        dummyFunctionAsync called 2 started
                Task 1 finished after 3 second.
                Task 2 finished after 3 second.
                Task 3 finished after 3 second.
        dummyFunctionAsync called 2 finished

forOf: ending

Ciclo ASYNC (En paralelo)

forEach

Las llamadas se ejecutan de forma simultanea

const items = [1, 2, 3];

// Ejecución en paralelo de las llamadas a dummyFunctionAsync
const mainFunctionWithForEach = () => {
  console.log("forEach: started");
  items.forEach(async (item, index) => {
    await dummyFunctionAsync(index);
  });
  console.log("forEach: ending");
};

Ejemplo de la salida en consola

forEach: started
        dummyFunctionAsync called 0 started
        dummyFunctionAsync called 1 started
        dummyFunctionAsync called 2 started
forEach: ending
                Task 1 finished after 3 second.
                Task 1 finished after 3 second.
                Task 1 finished after 3 second.
                Task 2 finished after 3 second.
                Task 2 finished after 3 second.
                Task 2 finished after 3 second.
                Task 3 finished after 3 second.
        dummyFunctionAsync called 0 finished

                Task 3 finished after 3 second.
        dummyFunctionAsync called 1 finished

                Task 3 finished after 3 second.
        dummyFunctionAsync called 2 finished

map

Las llamadas se ejecutan de forma simultanea, pero el map retorna las promesas que son almacenadas en un array, y podemos usar este array para "esperar" a que terminen todas las promesas antes de continuar

const items = [1, 2, 3];

// Ejecución en paralelo de las llamadas a dummyFunctionAsync
const mainFunctionWithMap = async () => {
  console.log("MAP: started");
  const promises = items.map(async (item, index) => {
    await dummyFunctionAsync(index);
  });
  console.log("MAP: ending");
  console.log("waiting for all the promises to end");
  const response = await Promise.all(promises);
  console.log("all promises finished");
};

Ejemplo de la salida en consola

MAP: started
        dummyFunctionAsync called 0 started
        dummyFunctionAsync called 1 started
        dummyFunctionAsync called 2 started
MAP: ending
waiting for all the promises to end
                Task 1 finished after 3 second.
                Task 1 finished after 3 second.
                Task 1 finished after 3 second.
                Task 2 finished after 3 second.
                Task 2 finished after 3 second.
                Task 2 finished after 3 second.
                Task 3 finished after 3 second.
        dummyFunctionAsync called 0 finished

                Task 3 finished after 3 second.
        dummyFunctionAsync called 1 finished

                Task 3 finished after 3 second.
        dummyFunctionAsync called 2 finished

all promises finished