Saltar al contenido

Clase 7 - Asincronismo, Promesas, Fetch

#javascript #fetch #async #promise #error

🕒 Sincronismo, Asincronismo, Promesas y Manejo de Errores en JavaScript

✅ Objetivos

  • Comprender la diferencia entre código síncrono y asíncrono.
  • Aprender cómo funciona el Event Loop de JavaScript.
  • Trabajar con Promesas (Promise), .then(), .catch().
  • Utilizar async/await para simplificar código asíncrono.
  • Manejar errores utilizando try/catch y throw.
  • Interactuar con APIs y operaciones de red usando fetch.
  • Crear y lanzar clases de error personalizadas.

✨ Sincronismo vs Asincronismo

🔹 Código Síncrono

Cada instrucción espera a que la anterior se complete antes de ejecutarse. Fácil de leer y seguir, pero puede bloquear el hilo con operaciones costosas.

console.log("Inicio");
console.log("Procesando...");
console.log("Fin");

Salida:

Inicio
Procesando...
Fin

🔹 Código Asíncrono

Permite que el programa continúe ejecutándose mientras espera a que ciertas tareas se completen. Ideal para operaciones como llamadas a servidores, lectura de archivos o temporizadores.

console.log("Inicio");
setTimeout(() => {
console.log("Tarea asincrónica completada");
}, 600000);
console.log("Fin");

Salida esperada:

Inicio
Fin
Tarea asincrónica completada

Funciones como setTimeout, fetch y los eventos del navegador son asíncronas y no bloquean el hilo principal.


🔄 El Event Loop

JavaScript se ejecuta en un solo hilo, pero el Event Loop permite operaciones no bloqueantes. Este mecanismo gestiona eficientemente múltiples tareas.

https://media.licdn.com/dms/image/v2/D5612AQHIuZDc3cqPtg/article-cover_image-shrink_600_2000/article-cover_image-shrink_600_2000/0/1721189705579?e=2147483647&v=beta&t=7z1ivEBMlIOpeq4P2UUbbrj1T64ysIpkPv27efVvq60

Conceptos clave:

  • Call Stack: Pila LIFO Last In First Out (último en entrar, primero en salir) donde se ejecutan las funciones.
  • Web APIs: Entorno proporcionado por el navegador para manejar tareas externas (temporizadores, solicitudes, etc).
  • Callback Queue: Cola de funciones esperando ser ejecutadas una vez que la pila esté libre.
  • Event Loop: Mueve tareas desde la cola hasta la pila cuando ésta se vacía.

Ejemplo:

console.log("1");
setTimeout(() => console.log("2"), 0);
console.log("3");

Salida:

1
3
2

Incluso con 0ms, setTimeout se aplaza hasta que la pila esté despejada.


🧪 Promesas (Promise)

Una Promesa es un objeto que representa el éxito o fracaso eventual de una operación asíncrona. Estructura el código asíncrono de forma clara.

Estados posibles:

  • pending (pendiente)
  • fulfilled (cumplida)
  • rejected (rechazada)

Sintaxis básica:

const promise = new Promise((resolve, reject) => {
const success = false;
setTimeout(() => {
if (success) {
resolve("Se resolvió");
}
else {
reject("Error");
}
}, 1000);
});
promise
.then(value => console.log(value))
.catch(error => console.error(error));

Encadenamiento:

promise
.then(value => {
console.log(value);
return "Paso 2";
})
.then(value => console.log(value))
.catch(error => console.error("Algo falló:", error));

El encadenamiento evita el “callback hell” (infierno de callbacks).


⚙️ Async / Await

Sintaxis moderna para escribir código asíncrono que parece síncrono. Excelente para mejorar la legibilidad.

Ejemplo:

async function execute() {
try {
const result = await promise;
console.log("Resultado:", result);
} catch (error) {
console.error("Error capturado:", error);
}
}
execute();

Reglas:

  • await solo dentro de funciones async.
  • La ejecución se pausa hasta que la promesa se resuelve o rechaza.
  • Perfecto para operaciones en serie.

🧪 Fetch API

fetch permite realizar solicitudes HTTP de forma simple y moderna. Devuelve una Promesa que se resuelve con un objeto Response.

Ejemplo básico:

fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Error de red:", error));

Con async/await:

async function getPost() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error al obtener post:", error);
}
}
getPost();

Siempre maneja respuestas de red y respuestas no exitosas.


🚩 Manejo de Errores

Esencial para garantizar que la aplicación pueda reaccionar adecuadamente ante fallos.

Con .catch()

promise
.then(response => console.log(response))
.catch(error => console.error("Error:", error));

Con try/catch en funciones async:

async function getData() {
try {
const response = await fetch("https://api.example.com");
if (!response.ok) throw new Error("Respuesta no exitosa");
const data = await response.json();
console.log(data);
} catch (e) {
console.error("Falló la petición:", e);
}
}

Usando throw

function validateInput(value) {
if (!value) throw new Error("Entrada vacía");
return true;
}
try {
validateInput("");
} catch (e) {
console.error(e.message);
}

Errores personalizados

Extiende la clase Error para manejar escenarios específicos.

class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function processInput(value) {
if (typeof value !== "string") {
throw new ValidationError("Se esperaba un string");
}
}
try {
processInput(123);
} catch (e) {
if (e instanceof ValidationError) {
console.error("Validación fallida:", e.message);
} else {
console.error("Error desconocido:", e);
}
}

🧐 Ejemplo completo

function fetchUserData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.3;
if (success) resolve("Datos del usuario recibidos");
else reject("No se pudo obtener la información");
}, 1000);
});
}
async function main() {
try {
const data = await fetchUserData();
console.log("Éxito:", data);
} catch (error) {
console.error("Error al obtener datos:", error);
}
}
main();

📚 Patrones avanzados con Promesas

Promise.all

Ejecuta múltiples promesas en paralelo y espera a que todas se resuelvan:

const promesa1 = fetch('https://api.ejemplo.com/datos1');
const promesa2 = fetch('https://api.ejemplo.com/datos2');
const promesa3 = fetch('https://api.ejemplo.com/datos3');
Promise.all([promesa1, promesa2, promesa3])
.then(responses => Promise.all(responses.map(res => res.json())))
.then(datos => console.log('Todos los datos:', datos))
.catch(error => console.error('Al menos una promesa falló:', error));

Promise.race

Devuelve el resultado de la primera promesa que se complete (resuelta o rechazada):

const promesaRapida = new Promise(resolve => setTimeout(() => resolve('Rápida'), 500));
const promesaLenta = new Promise(resolve => setTimeout(() => resolve('Lenta'), 1000));
Promise.race([promesaRapida, promesaLenta])
.then(resultado => console.log('La primera en completarse fue:', resultado));

Promise.allSettled

Espera a que todas las promesas se completen independientemente de si se resuelven o rechazan:

const p1 = Promise.resolve('Éxito');
const p2 = Promise.reject('Error');
const p3 = new Promise(resolve => setTimeout(() => resolve('Tardío'), 1000));
Promise.allSettled([p1, p2, p3])
.then(resultados => {
resultados.forEach(resultado => {
if (resultado.status === 'fulfilled') {
console.log('Resuelta con:', resultado.value);
} else {
console.log('Rechazada con:', resultado.reason);
}
});
});

Ejercicios

  • Resolver el siguiente ejercicio:

Timer con bucle for

Hacer un timer que muestre en pantalla una cuenta del 1 al 10 mediante el uso de un bucle for. Una vez realizado el timer, verificar que es lo que sucede si en lugar de utilizar let para declarar el iterador del bucle, se usa var. Intentar corregir dicho comportamiento, considerando que parte es sincrónica del código y cual asincrónica.