🌱 Digital Garden

Search

Search IconIcon to open search

JavaScript Asincrono

Last updated Jan 22, 2023 Edit Source

Lidiar con tareas que tardan demasiado en finalizar. Un ejemplo popular de JavaScript asincrono es el AJAX.

Normalmente todo el codigo que escribimos en JavaScript es sincrono, es decir, se ejecuta linea por linea hasta terminar y proceder con las siguientes. Todos en el mismo thread.

El codigo asincrono nos permite ejecutar tareas en segundo plano de modo que estas no bloqueen nuestro hilo de ejecucion principal.

# Ajax

Asincronous JavaScript And XML. Nos permite comunicarnos con servidores y solicitar datos de forma asincrona.

# XMLHttpRequest

Es una forma clasica de enviar peticiones Ajax. Para esto utilizamos un objeto especial y agregamos un eventlistener que va a escuchar cuando los datos sean recibidos y ejecutara la callback function que tenemos.

1
2
3
4
5
6
7
const request = new XMLTHttpRequest();
request.open('GET', 'https://restcountries.eu/rest/v2/name/portugal')
request.send()

request.addEventListener('load', function () {
	console.log(JSON.parse(this.responseText))
})

# Callback Hell

Es un concepto que se refiere a lo que ocurre cuando quieres realizar multiples eventos asincronos en forma seriada, es decir, que se ejecuten uno despues del otro. Esto telleva a tener una gran cantidad de callback functions dentro de otras callback functions.

Es facil de reconocer debido a la estructura que este tiene

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
setTimeout(function () {
	console.log(1);
	setTimeout(function () {
		console.log(2);
		setTimeout(function () {
			console.log(3);
			setTimeout(function () {
				console.log(4);
				setTimeout(function () {
					console.log(5);
				}, 1000)
			}, 1000)
		}, 1000)
	}, 1000)
}, 1000);

La forma mas comun de evitarlo es utilizando una caracteristica de ES6 llamada Promises

# Promises

Es una caracteristica de JavaScript moderno que viene a ayudar a escapar del Callback Hell.

Una Promise es un objeto que es usado como placeholder de resultado de una operacion asincrona, es decir, se puede tratar a la Promise como si el codigo asincrono ya hubiese sido ejecutado. Contenedor para un valor futuro.

  1. No necesitamos de eventos y callback functions para lidiar con codigo asincrono.
  2. Podemos encadenar multiples promises para crear una secuencia de operaciones asincronas, escapando del callback hell.

# Lifecycle

Una promise tambien tiene un ciclo de vida:

  1. Pending. La tarea asincrona aun no ha finalizado.
  2. Settled. Una tarea asincrona ha finalizado. Pueden ser de dos estados
    1. Fulfilled. La tarea finalizo cone xito.
    2. Rejected. La tarea tuvo una excepcion entre medio.

# Consumir una Promise

Para consumir una Promise tenemos varias opciones. La primera y mas comun es utilizar el metodo “then()” de las Promise.

1
2
3
4
const promise = fetch('url');
promise.then(function(response) {
	console.log(response)
})

# Lidiar con una Rejected Promise

Existen dos formas de lidiar con una Rejected Promise

  1. Colocar una callback function extra en el metodo “.then()”
  2. Agregar el metodo “catch” al final de las Promise
1
2
3
fetch('url').then('sucessCallback', 'rejectedCallback');

fetch('url').then('successCallback').catch('rejectedCallback');

Adicionalmente, tambien tenemos el metodo “finally” que se ejecuta sin importar el resultado de la Promise.

# Errores

Podemos tirar errores utilizando la keyword ’throw’ que nos permite lanzar una excepcion.

# Construir una Promise

Para construir una Promise podemos utilizar su constructor con el operador new.

En el econstructor, lo primero que debemos indicar es una ExecutorFunction la cual sera ejecutada inmediatamente al construir la Promise.

Esta ExecutorFunction a su vez, recibe otras dos funciones como parametro, la primera en el caso de resolve y la segunda en el caso de reject.

En el contenido de la ExecutorFunction se debe encontrar el codigo que ocurrira de forma asincrona Nunca se utiliza el statement return, en su lugar, se utilizan las funciones resolve y reject pasando el valor que nosotros queremos que estas obtengan.

Los valores que les pasemos a las llamadas de estas funciones dentro del ExecutorFunction seran los valores que estaran disponibles desde fuera de la promesa en los metodos “.then” y “.catch”.

# Fetch API

Es una API que vino a reemplazar al antiguo XMLHttpRequest. Introducida en ES6 es una forma bastante importante para obtener datos desde una API.

1
2
const request = fetch('https://restcountries.eu/rest/v2/name/portugal');
console.log(request);

Por defecto, la Fetch API retorna una Promise.

# Event Loop

Es una parte del JavaScript runtime que pasa elementos del Callback Queue al Call Stack para su ejecucion.

Todos los eventos asincronos ocurren dentro del ambiente de una web api, es decir, estos no son pasados al call stack para su ejecucion.

Una vez una web api completa una tarea asincrona, esta es insertada en el callback queue y esta espera su turno.

El Event Loop posteriormente mueve los elementos del callback queue hacia el call stack una vez esta ultima se encuentra vacia es decir, el event loop es un orquestrador que cooordina el call stack y call back queue.

Una cosa importante a entender esque los eventListeners no ocurren en el call stack. Estos son unidos junto con su respectivo evento y esperan a que ocurra dentro de la API, una vez un evento es trigereado, su callback function entra en el callback queue y el evento loop espera a que el call stack este vacio para moverlo y que proceda su ejecucion.

Con una Promise funciona algo diferente, las callbacks relacionadas con unas Promise no entran al callback queue, entran a una queue especial para las Promises.

Esta queue especial es llamada microtasks queue. Todos los elementos que se encuentran dentro del microtasks queue son ejecutados primero antes que los elementos del callback queue.

# Async y Await

Lo primero que se debe hacer es crear una funcion especial asincrona.

1
2
3
const whereAmI = async function (country) {
	
}

Dentro de una funcion async podemos tener uno o varios await statements.

1
2
3
const func = async function () {
	await fetch('url');
}

Utilizando estas nos deshacemos del chaining de .then, .catch, etc.

Async y Await solo es syntactic sugar que nos sirve para consumir Promises de la forma antigua utilizando los metodos .then y catch, etc.

# try-catch

En vez de utilizar el metodo catch (debido a que no disponmemos de el gracias a esta nueva sintaxis). Podemos utilizar try… catch para salvaguardar el codigo!

1
2
3
4
5
6
7
const func = async function() {
	try {
		const d = await fetch('ASDASD');
	} catch (err) {
		console.error(err)
	}
}

# Retornar valores desde Async Functions

Una Async function siempre returna una Promise, debido a que esta se trata de una funcion asincrona. Es por ello que, si queremos retornar datos, debemos tratarlos como tal.

1
2
3
4
5
6
7
const func = async function () {
	return '1';
}

console.log('1')
func().then(resp => console.log(resp));
console.log('2')

Una cosa a tener en cuenta esque si tenemos un error dentro de nuestra funcion asincrona y tenemos un bloque de try… catch para detenerlo, debemos propagarlo retirando el error nuevamente para que la Promise sea marcada como Rejected.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const funcI = async function () {
	try {
		// code ...
		// error...
	} catch (err) {
		// INCORRECTO
		console.error(err);
	}
}

const funcV = async function () {
	try {
		// code ...
		// error...
	} catch (err) {
		// CORRECTO
		console.error(err);
		throw err;
	}
}

Si queremos evitar combinar la sintaxis de las Promises (.then, .catch, etc) podemos utilizar una IIFE para que lidie con la respuesta de una funcion asincrona

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(async function() {
    try {
        const city = await whereAmIAsync();
        console.log(`2: ${city}`);
    } catch (err) {
        console.err(`2: ${err}`);
    } finally {
        console.log(`3: Finished getting location`);
    }
})();

# Paralelizacion = Promise.all

Una forma de correr multiples promises en paralelo, que no dependen la una de la otra, debemos utilizar la funcion especial Promise.all que recibe un array de promises y nos permite ejecutar todas ellas en paralelo.

1
2
const data = await Promise.all(promsie1, promise2, promise3);
console.log(data);

Algo importante a tener en cuenta esque este combinador hace shortcircuiting en cuanto una de las Promise reciba el estado rejected.

# Promise.race

Como otros combinadores, recibe un array de promises y el array es retornado en cuanto una de las tres promises tiene el estado “Settled” (sin importar si es rechazada o aprobada).

1
2
3
4
5
6
7
8
(async function() {
	const res = await Promise.race([
		promise1,
		promise2,
		promise3
	]);
	console.log(res[0]);
})()

En el codigo de arriba, cualquiera de las tres puede ser rellenada primero, y la respuesta sera dicho elemento, sin importar si fue rechazada o cumplida.

# Promise.allSettled

Es simplemente otro combinador que recibe un array de promises y retornara otro array de promises con todas las que fueron Settled. Al contrario de .all, este no hace short-circuit si una de ellas es rejected, simplemente las ejecuta todas sin improtar sus resultados.

1
2
3
4
5
Promise.allSettled([
	Promise.resolve('a'),
	Promise.rejec('error'),
	Promise.resolve('a'),
]).then(res => console.log(res));

# Promise.any

Es un combinador bastante sencillo que igualmente recibe un array de promises y termina de ejecutarse en cuanto cualquiera de las Promise reciba un estado Fulfilled, ignorando todas las Promises con estado Reject.

1
2
3
4
5
Promise.allSettled([
	Promise.reject('a'),
	Promise.rejec('error'),
	Promise.resolve('a'),
]).then(res => console.log(res));

# Top-Level await

En ES2022 ahora es posible utilizar await fuera de cualquier funcion sin embargo, esto unicamente se encuentra disponible en los modulos modernos.

1
2
3
const res = await fetch('url');
const data = await res.json();
console.log(data);

Lo importante de entender esque esta caracteristica bloquea la ejecucion del modulo, es decir que en cierta forma se vuelve sincrono.

Si un modulo importa otro modulo que tiene un top-level await, este esperara a que se termine de ejecutar para seguir con su carga.