Asincronismo

Conceptos básicos

API

Interfaz de programación de aplicaciones (Application Programming Interface). Es un conjunto de rutinas que provee acceso a funciones de un determinado software.

Concurrencia

Cuando dos o más tareas progresan simultáneamente.

Paralelismo

Cuando dos o más tareas se ejecutan, literalmente, a la vez, en el mismo instante de tiempo.

Bloqueante

Una llamada u operación bloqueante no devuelve el control a nuestra aplicación hasta que se ha completado. Por tanto el thread queda bloqueado en estado de espera.

Síncrono

Es frecuente emplear ‘bloqueante’ y ‘síncrono’ como sinónimos, dando a entender que toda la operación de entrada/salida se ejecuta de forma secuencial y, por tanto, debemos esperar a que se complete para procesar el resultado.

Asíncrono

La finalización de la operación I/O se señaliza más tarde, mediante un mecanismo específico como por ejemplo un callback, una promesa o un evento, lo que hace posible que la respuesta sea procesada en diferido.

Heap

Región de memoria libre, normalmente de gran tamaño, dedicada al alojamiento dinámico de objetos. Es compartida por todo el programa y controlada por un recolector de basura que se encarga de liberar aquello que no se necesita.

Call Stack

La pila de llamadas o Call Stack, se encarga de albergar las instrucciones que deben ejecutarse. Nos indica en que punto del programa estamos, por donde vamos. Es una estructura de datos en la que el último elemento agregado es siempre el primero en eliminarse de la pila, podría pensar en ella como una pila de un plato en el que solo se puede quitar primero el primer plato que fue el último agregado. Una pila de llamadas no es más que una estructura de datos de pila donde las tareas o el código se ejecutan en consecuencia.

El call stack (pila de llamadas) en JavaScript sigue una política LIFO (Last In, First Out) esto significa que la última función que se añade al stack es la primera en ser ejecutada y completada. La ejecución de funciones sigue este orden:

  1. Empuje (Push): Cuando una función es llamada, se coloca (empuja) en la parte superior del call stack.

  2. Desempuje (Pop): Cuando una función termina su ejecución, se retira (desempuja) de la parte superior del call stack.

Ejemplo

Consideremos el siguiente código:

function funcionA() {  
    console.log("A empieza");  
    funcionB();  
    console.log("A termina");
}

function funcionB() {  
    console.log("B empieza");  
    funcionC();  
    console.log("B termina");
}

function funcionC() {  
    console.log("C empieza y termina");
}

funcionA();

Análisis del Call Stack

  1. Inicio

    • funcionA es llamada y se coloca en el call stack.

    • Call stack: [funcionA]

  2. Ejecución de funcionA

    • console.log("A empieza") se ejecuta.

    • funcionB es llamada y se coloca en el call stack.

    • Call stack: [funcionA, funcionB]

  3. Ejecución de funcionB

    • console.log("B empieza") se ejecuta.

    • funcionC es llamada y se coloca en el call stack.

    • Call stack: [funcionA, funcionB, funcionC]

  4. Ejecución de funcionC

    • console.log("C empieza y termina") se ejecuta.

    • funcionC termina y se retira del call stack.

    • Call stack: [funcionA, funcionB]

  5. Continuación de funcionB

    • console.log("B termina") se ejecuta.

    • funcionB termina y se retira del call stack.

    • Call stack: [funcionA]

  6. Continuación de funcionA

    • console.log("A termina") se ejecuta.

    • funcionA termina y se retira del call stack.

    • Call stack: []

Salida del Código

A empieza
B empieza
C empieza y terminaB 
termina
A termina

Cola de tareas o Queue Task

Una cola de tareas es una estructura de datos que funciona según el principio el primero en entrar, es el primero en salir FIFO (First In, First Out), de modo que a medida que las tareas se introducen en la cola, salen en el mismo orden. Cada vez que nuestro programa recibe una notificación del exterior o de otro contexto distinto al de la aplicación (como de una llama a un servicio web), se envían a la cola de tareas, luego regresan a la pila de llamadas para imprimir su resultado.

Eventloop o Bucle de Eventos

El bucle de eventos (Event Loop) es un proceso que utiliza el navegador internamente para manejar la ejecución de procesos o tareas que hay en el código fuente. El Event Loop va evaluando las tareas y las carga en el Call Stack o si son tareas bloqueantes las envia a esperar en un proceso aparte que se llama Thread Pool. De ahí las tareas que se van resolviendo y pasan al Queue Task, a esperar su turno en la pila de ejecución. Luego el EventLoop espera a que la pila de llamadas (Call Stack) se vacíe, una vez que la pila está limpia, el ciclo de eventos se activa de nuevo y verifica la cola de tareas (Queue Task) para ver si hay operaciones resueltas disponibles. Si hay alguna, lo empuja a la Pila de llamadas (Call Stack) y las ejecuta, luego repite el mismo proceso las veces que sea necesario.

Definición estructura callback

Una función que recibe otra función como parámetro se le denomina función de orden superior (higher-order function).

El callback en este caso sería la función que es pasada como parámetro, mas no la función que lo recibe. Sirve o servia para esperar respuestas del servidor.

function doSomething(val, callback) {
    val += 1; // or call an API with XMLHttpRequest
    callback(val);
}

doSomething(1, (response) => {
    // callback Response
});

Promesas

Las promesas en JavaScript representan procesos que ya están sucediendo, que se pueden encadenar con funciones de devolución de llamada. Mejoran la manipulacion de llamadas asincronas a diferencia de las callbacks.

const somethingWillHappen = () => {
  return new Promise((resolve, reject) => {
    if (true) {
      resolve("Hey!");
    } else {
      reject("Whoops!");
    }
  });
};

somethingWillHappen()
  .then((response) => {
    console.log(response); // Hey!
  })
  .catch((err) => {
    console.error(err);
  });

Métodos de las promesas

Promise.all(iterable)

Recibe como parámetro promesas las cuales se ejecutan simultáneamente, si alguna de estas es rechazadas entonces toda la lista lo es, pero si todas se resuelven entonces podemos obtener una lista de todas las respuestas.

Si la promesa retornada es cumplida, lo hace con un arreglo de los valores de las promesas cumplidas en el mismo orden definido en el iterable.

Si la promesa retornada es rechazada, es rechazada con la razón de la primera promesa rechazada en el iterable.

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, "foo");
});

Promise.all([p1, p2, p3]).then(values => {
  console.log(values); // [3, 1337, "foo"]
});

//

var p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, "one");
});
var p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 2000, "two");
});
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 3000, "three");
});
var p4 = new Promise((resolve, reject) => {
  setTimeout(resolve, 4000, "four");
});
var p5 = new Promise((resolve, reject) => {
  reject("reject");
});

Promise.all([p1, p2, p3, p4, p5]).then(values => {
  console.log(values);
}, reason => {
  console.log(reason)
});

//From console:
//"reject"

Promise.race(iterable)

Nos permite correr varias promesas al tiempo, pero solo va a obtener el resultado de la que se resuelva primero.

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, "foo");
});

Promise.race([p1, p2, p3]).then(values => {
  console.log(values); // 3
});

Async/await

La finalidad de las funciones async/await es simplificar el comportamiento del uso síncrono de promesas y realizar algún comportamiento específico en un grupo de Promise. Del mismo modo que las Promesas son semejantes a las devoluciones de llamadas estructuradas, async/await se asemejan a una combinación de generadores y promesas.

const doSomethingAsync = () => {
  return new Promise((resolve, reject) => {
      true ? setTimeout(() => resolve('Do something async'), 3000) : reject(new Error('Test error'));
  });
};

const doSomething = async () => {
  const something = await doSomethingAsync();
  console.log(something);
};

console.log('Before');
doSomething();
console.log('After');

Last updated