JavaScript Asincrónico

Algunas cosas suceden instantáneamente: sumar dos números, leer una variable, cambiar texto en la página. Otras toman tiempo. Un temporizador cuenta regresivamente durante tres segundos. Una solicitud a un servidor viaja por la red y regresa un momento después. Si JavaScript se detuviera y esperara por cada una de esas, toda la página se congelaría: sin clics, sin desplazamiento, nada, hasta que la cosa lenta terminara. No funciona así. Este capítulo trata sobre cómo JavaScript inicia algo lento, mantiene la página receptiva, y regresa para manejar el resultado cuando está listo.
Por qué async
La mayoría del JavaScript se ejecuta de arriba a abajo, una línea terminando antes de que la siguiente comience. Eso está bien para el trabajo rápido. Pero algunas tareas toman tiempo real, y JavaScript no se sienta y espera por ellas. Inicia la tarea lenta, se mueve a la siguiente línea, y regresa a la lenta cuando esté hecha. Eso es lo que significa asincrónico: no sucediendo en orden, no ahora mismo, pero después, cuando el resultado está listo.
El ejemplo más simple es un temporizador. setTimeout ejecuta un código después de un retraso:
console.log("Inicio");
setTimeout(() => {
console.log("3 segundos después");
}, 3000);
console.log("Fin");
// Inicio
// Fin
// 3 segundos despuésNota el orden. "Fin" se imprime antes de "3 segundos después", aunque viene después de setTimeout en el código. JavaScript no esperó tres segundos en esa línea. Estableció el temporizador, siguió adelante, y ejecutó el código retrasado cuando el tiempo se agotó.
"Fin" se imprime antes del mensaje de setTimeout, aunque el temporizador viene primero en el código. Promesas
setTimeout está bien para un retraso, pero la mayoría del trabajo async produce un valor que te importa: los datos que solicitaste, o un error si algo salió mal. Para eso, JavaScript usa una promesa. Una promesa es un valor que aún no está listo, con un lugar reservado para él. Es como un recibo: no tienes la comida, pero tienes algo que se convertirá en la comida.
Una promesa termina de una de dos formas. Se resuelve con un valor cuando el trabajo tiene éxito, o rechaza con un error cuando falla. Dices qué hacer en cada caso con .then() y .catch():
loadUser()
.then((user) => {
console.log(`Cargado ${user.name}`);
})
.catch((error) => {
console.log("Algo salió mal");
});.then() se ejecuta cuando la promesa se resuelve, y recibe el valor. .catch() se ejecuta si rechaza, y recibe el error. El código dentro de ellos se ejecuta después, una vez que el resultado llega.
.then() para el valor y .catch() para el error, y ambos se ejecutan después una vez que el resultado está. async / await
Las cadenas de promesas funcionan, pero hay una forma más limpia de escribirlas que se lee casi como código ordinario de arriba a abajo. Utiliza dos palabras clave, async y await.
Marcas una función async, y dentro de ella puedes await una promesa. await pausa la función hasta que la promesa se resuelve, luego te da el valor directamente, sin necesidad de .then():
async function showUser() {
const user = await loadUser();
console.log(`Cargado ${user.name}`);
}Lee eso de arriba a abajo: obtén el usuario, luego registra el nombre. La línea await espera a que loadUser() termine y te da el user. La función se pausa allí, pero el resto de la página sigue ejecutándose, así que nada se congela.
Para manejar errores, envuelve el await en try y catch:
async function showUser() {
try {
const user = await loadUser();
console.log(`Cargado ${user.name}`);
} catch (error) {
console.log("No se pudo cargar el usuario");
}
}Si la promesa falla, el código salta al bloque catch en lugar de bloquearse.
async y puedes await una promesa dentro de ella. await pausa esa función hasta que la promesa se resuelve y te da el valor de inmediato, así que el código se lee de arriba a abajo sin .then(). Envuélvelo en try y catch para manejar fallas, y la página sigue ejecutándose mientras la función espera. Obtener datos
La tarea async más común que escribirás es cargar datos desde un servidor. El navegador te da fetch para esto. Le pasas una URL, y devuelve una promesa para la respuesta:
async function loadUsers() {
const response = await fetch("https://api.example.com/users");
const users = await response.json();
console.log(users);
}Hay dos await, y eso sorprende a la gente al principio. El primero espera a que el servidor responda. El segundo lee el cuerpo de esa respuesta y la convierte en datos de JavaScript con .json(), que en sí es async. Así que obtener datos es dos pasos: obtener la respuesta, luego leerla.
Los servidores también pueden responder con un error, como "no encontrado". fetch no trata eso como un fallo por sí solo, así que verificas response.ok tú mismo:
async function loadUsers() {
const response = await fetch("https://api.example.com/users");
if (!response.ok) {
console.log("Solicitud falló");
return;
}
const users = await response.json();
console.log(users);
}fetch(url) devuelve una promesa para la respuesta del servidor, y cargar datos es dos pasos: await fetch(...) para la respuesta, luego await response.json() para leer los datos de ella. Un error del servidor como "no encontrado" no falla por sí solo, así que verifica response.ok antes de leer el cuerpo. Ambos pasos usan await porque ambos toman tiempo. El event loop y ejecutar trabajo en paralelo
Todo hasta ahora tiene un tema: JavaScript inicia trabajo lento, sigue adelante, y regresa a él después. Esta sección es el mecanismo detrás de eso, más los patrones para ejecutar varias tareas async bien.
Aquí hay una imagen de cómo JavaScript mantiene un registro del trabajo async. Tu código en ejecución se sienta en la pila de llamadas, la lista de lo que se está ejecutando en este momento. Cuando llamas a setTimeout, el temporizador se entrega al navegador para contar regresivamente, así que no está en la pila bloqueando nada. Cuando el temporizador termina, su callback va a una cola de tareas, una línea de espera de código listo para ejecutarse.
El event loop es la parte que los conecta. Tiene una regla: ejecuta todo en la pila de llamadas primero, y solo cuando la pila está vacía extrae la siguiente tarea de la cola. Por eso setTimeout(fn, 0) aún se ejecuta después de tu otro código, incluso con un retraso de cero. El callback tiene que esperar en la cola hasta que el código actual esté hecho:
console.log("primero");
setTimeout(() => console.log("tercero"), 0);
console.log("segundo");
// primero
// segundo
// terceroEl retraso de 0 no significa "ahora mismo". Significa "tan pronto como el código actual termine".
setTimeout(fn, 0) aún se ejecuta después del resto de tu código. Un retraso de 0 significa "tan pronto como el código actual termine", no "ahora mismo". Hacia dónde va async desde aquí
Async es la parte de JavaScript que mantiene una página viva mientras el trabajo lento sucede en segundo plano. La forma es siempre la misma: inicia algo que toma tiempo, deja que el resto de tu código se ejecute, y maneja el resultado cuando llega. Las promesas le dan a ese resultado un valor que puedes pasar, async/await te deja escribirlo en código plano de arriba a abajo, y fetch es donde usarás todo esto más, cargando datos desde un servidor.
Desde aquí, async se conecta con las partes del lenguaje que reaccionan al mundo exterior. Conectarás datos obtenidos en la página a través del DOM, y comenzarás trabajo async en respuesta a lo que la gente hace en la página, que son eventos. Los resultados que obtengas de vuelta son casi siempre objetos, así que leer y reformular ellos es la siguiente habilidad que se empareja con todo aquí.

