El event loop: síncrono vs asíncrono
JavaScript ejecuta UNA cosa cada vez: es single-threaded. Si algo tarda (una descarga, un temporizador), no puede quedarse ahí esperando porque la página se congelaría. La solución: delegar. El navegador realiza la tarea lenta y, cuando termina, deja el resultado en una cola; el event loop la va desaguando cuando el hilo principal queda libre.
console.log("1 · síncrono");
setTimeout(() => {
console.log("2 · asíncrono");
}, 0);
console.log("3 · síncrono");
// Resultado: 1, 3, 2
// ¡el timeout de 0 ms NO va primero! Se apunta a la cola
// y espera a que termine TODO el código síncrono.
setTimeout(…, 0) nunca se ejecuta «al instante»: se ejecuta cuando el
hilo principal se despeja.1.1 · Temporizadores: setTimeout y setInterval
| Método | Qué hace | Devuelve |
|---|---|---|
setTimeout(fn, ms) | ejecuta fn UNA vez, tras ms milisegundos | id para cancelarlo |
setInterval(fn, ms) | ejecuta fn EN BUCLE, cada ms milisegundos | id para cancelarlo |
clearInterval(id) | detiene el intervalo (también existe clearTimeout) | — |
const alarma = setTimeout(() => {
console.log("¡Han pasado 3 segundos!");
}, 3000);
let segundos = 0;
const id = setInterval(() => {
segundos++;
console.log(segundos);
if (segundos === 5) clearInterval(id);
}, 1000);
clearInterval, un intervalo corre eternamente consumiendo batería y memoria:
es la fuga de recursos más típica del front-end.Promesas: then, catch y finally
Una promesa es el recibo de algo que estás esperando: te dan el papel
AHORA y el producto CUANDO esté. Es el estándar moderno para trabajar con tareas
asíncronas — y la base sobre la que funciona fetch.
| Estado | Significado | Tu callback |
|---|---|---|
pending | pendiente: aún no hay respuesta | — |
fulfilled | cumplida: llegó el dato | .then(valor => …) |
rejected | rechazada: algo falló | .catch(error => …) |
const pedido = new Promise((resolve, reject) => {
setTimeout(() => {
const exito = Math.random() > 0.3;
if (exito) resolve("🍕 Pizza lista");
else reject("🔥 Se quemó");
}, 1500);
});
pedido
.then((mensaje) => console.log(mensaje))
.catch((error) => console.log(error))
.finally(() => console.log("Pedido cerrado"));
2.1 · Encadenar promesas: la alternativa al infierno de callbacks
Cuando una tarea depende de la anterior, en el mundo antiguo anidabas callbacks hasta
formar una pirámide ilegible. Con promesas, cada .then puede
devolver un valor u otra promesa, y el siguiente
.then lo recibe:
getUsuario(7, (user) => {
getPedidos(user.id, (pedidos) => {
getDetalle(pedidos[0], (detalle) => {
console.log(detalle);
});
});
});
getUsuario(7)
.then((user) => getPedidos(user.id))
.then((pedidos) => getDetalle(pedidos[0]))
.then((detalle) => console.log(detalle))
.catch((err) => console.log("Falló:", err));
.catch al final de la cadena captura el fallo de
CUALQUIER eslabón: no necesitas un control de errores por paso. Esa es la gran magia de
las promesas.finally se ejecuta SIEMPRE, haya ido bien o mal. Es el sitio
ideal para quitar un spinner de «cargando…».fetch: datos de APIs reales
Una API REST es una web que, en vez de páginas, responde con
datos JSON. Y fetch() es la función nativa del navegador para
pedirle cosas. Devuelve — como ya sospechas — una promesa.
Trabajaremos con Open-Meteo, una API meteorológica gratuita, sin clave y abierta a cualquier web (ideal para aprender). Dos endpoints:
// 1) Geocodificación: convierte "Valencia" en coordenadas
https://geocoding-api.open-meteo.com/v1/search?name=Valencia&language=es&count=1
// 2) Tiempo actual: necesita latitud y longitud
https://api.open-meteo.com/v1/forecast?latitude=39.47&longitude=-0.38¤t_weather=true
? son query params
(clave=valor unidos por &): le dicen a la API QUÉ quieres y CÓMO lo quieres.3.1 · El ritual completo: fetch → ok → json
Toda petición sigue TRES pasos. Memorízalos, son siempre iguales:
- Pedir:
fetch(url)lanza la petición y promete una respuesta. - Comprobar:
response.okvale true solo si el servidor respondió bien (status 200–299). - Traducir:
response.json()parsea el cuerpo JSON a un objeto usable.
fetch(URL)
.then((r) => r.json())
.then((data) => console.log(data));
// con un 404 esto pinta basura sin avisar
fetch(URL)
.then((r) => {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.json();
})
.then((data) => console.log(data.current_weather))
.catch(() => console.log("Sin conexión"));
3.2 · Explorar la respuesta
La primera vez ante una API nueva, abre la URL en el navegador y estudia la forma del
JSON: ¿es un objeto o un array? ¿Dónde está el dato que buscas? Para Open-Meteo,
la temperatura vive en data.current_weather.temperature:
{
"latitude": 39.5,
"current_weather": {
"temperature": 24.6, // ← data.current_weather.temperature
"windspeed": 13.2,
"weathercode": 0
}
}
async/await, errores y paralelismo
async/await es azúcar sintáctico sobre las promesas: el mismo poder, pero
escrito como código síncrono. Se lee de arriba a abajo y el cerebro lo agradece:
const cargarTiempo = () => {
fetch(URL)
.then((r) => r.json())
.then((data) => pintar(data));
};
const cargarTiempo = async () => {
const r = await fetch(URL);
const data = await r.json();
pintar(data);
};
4.1 · Las dos reglas de oro
await debe marcarse async.async devuelve, ella misma, una promesa.4.2 · try/catch: el cinturón de seguridad
Dentro de una función async, un try/catch normal captura tanto errores de
red como respuestas rechazadas — adiós a los .catch sueltos:
const buscar = async (ciudad) => {
aviso.textContent = "Buscando…"; // estado de carga
try {
const r = await fetch(urlDe(ciudad));
const data = await r.json();
if (!data.results) {
aviso.textContent = "Ciudad no encontrada";
return;
}
pintarResultado(data);
} catch (error) {
aviso.textContent = "Error de conexión";
}
};
4.3 · Varias peticiones a la vez: Promise.all
Si necesitas varios datos independientes, lanzarlos en serie desperdicia tiempo.
Promise.all los lanza en paralelo y espera a que terminen todos:
const urls = [urlValencia, urlMadrid, urlBarcelona];
const respuestas = await Promise.all(urls.map((u) => fetch(u)));
const datos = await Promise.all(respuestas.map((r) => r.json()));
datos.forEach((d) => console.log(d.current_weather.temperature));
Promise.allSettled y Promise.any — te suenan cuando las veas.✍️ Ejercicios guiados
Crea una carpeta bloque7. Cada ejercicio es un archivo HTML propio
(con su <script> al final del body). Necesitarás conexión a internet
desde el ejercicio 7 en adelante.
Sección A · Timers y el event loop
1. orden-ejecucion.html — Tres
console.log: «A» síncrono, luego un setTimeout(…, 0) que pinte
«B», y después «C». ESCRIBE primero tu predicción en un comentario y compuébala.
Ver solución
// Predicción: A, C, B — el timeout va a la cola
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
console.log("C");2. cuenta-atras.html — Una cuenta atrás de
10 a 0 en consola con setInterval (un número por segundo). Al llegar a 0,
detén el intervalo con clearInterval y pinta «🚀 Despegue».
Ver solución
let n = 10;
const id = setInterval(() => {
console.log(n);
n--;
if (n < 0) {
clearInterval(id);
console.log("🚀 Despegue");
}
}, 1000);3. reloj.html — Un reloj en un
<h1>: cada segundo muestra la hora actual
(new Date().toLocaleTimeString()). Añade un botón «Detener» que pare el
reloj con clearInterval.
Ver solución
const pantalla = document.querySelector("#hora");
const btnParar = document.querySelector("#parar");
const id = setInterval(() => {
pantalla.textContent = new Date().toLocaleTimeString();
}, 1000);
btnParar.addEventListener("click", () => clearInterval(id));Sección B · Promesas
4. promesa-basica.html — Crea una promesa
que espere 2 segundos (setTimeout dentro) y resuelva con el texto
«Datos listos». Consume con .then y usa .finally para limpiar
un párrafo «Cargando…».
Ver solución
const estado = document.querySelector("#estado");
estado.textContent = "Cargando…";
const tarea = new Promise((resolve) => {
setTimeout(() => resolve("Datos listos"), 2000);
});
tarea
.then((mensaje) => console.log(mensaje))
.finally(() => { estado.textContent = ""; });5. promesa-rechazo.html — Una función
comprobarEdad(edad) que devuelve una promesa: resuelve con «Acceso ok» si
edad ≥ 18 y rechaza con «Menor de edad» en caso contrario. Consume dos veces (18 y 15)
mostrando el resultado o el error.
Ver solución
const comprobarEdad = (edad) =>
new Promise((resolve, reject) => {
if (edad >= 18) resolve("Acceso ok");
else reject("Menor de edad");
});
comprobarEdad(18)
.then((msg) => console.log("✔", msg))
.catch((err) => console.log("✘", err));
comprobarEdad(15)
.then((msg) => console.log("✔", msg))
.catch((err) => console.log("✘", err));6. cadena-promesas.html — Simula tres pasos
de servidor con funciones que devuelven promesas (resuelven tras 300 ms):
getUsuario() → getCiudad(usuario) →
getTiempo(ciudad). Encadénalas con .then y añade un único
.catch común.
Ver solución
const esperar = (ms) => new Promise((res) => setTimeout(res, ms));
const getUsuario = async () => {
await esperar(300);
return { nombre: "Ada", ciudad: "Valencia" };
};
const getCiudad = async (usuario) => {
await esperar(300);
return usuario.ciudad;
};
const getTiempo = async (ciudad) => {
await esperar(300);
return `${ciudad}: 24 ºC y sol`;
};
getUsuario()
.then((usuario) => getCiudad(usuario))
.then((ciudad) => getTiempo(ciudad))
.then((resumen) => console.log(resumen))
.catch(() => console.log("Falló la cadena"));Sección C · fetch y APIs
7. fetch-basico.html — Pide el tiempo actual
de Valencia con coordenadas fijas
(latitude=39.47&longitude=-0.38¤t_weather=true) y muestra en
consola la temperatura y la velocidad del viento.
Ver solución
const url =
"https://api.open-meteo.com/v1/forecast" +
"?latitude=39.47&longitude=-0.38¤t_weather=true";
fetch(url)
.then((r) => r.json())
.then((data) => {
const ahora = data.current_weather;
console.log("Temperatura:", ahora.temperature);
console.log("Viento:", ahora.windspeed, "km/h");
});8. fetch-error.html — Repite el ejercicio 7
pero profesional: comprueba response.ok, lanza un Error si no procede y
gestiona el fallo con .catch mostrando «Sin conexión» en un párrafo.
Ver solución
const aviso = document.querySelector("#aviso");
fetch(url)
.then((r) => {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.json();
})
.then((data) => console.log(data.current_weather))
.catch(() => {
aviso.textContent = "Sin conexión";
});9. geocoding.html — Pide a
geocoding-api.open-meteo.com/v1/search?name=Madrid&language=es&count=1
y pinta en un <ul> el nombre, la latitud y la longitud del primer
resultado. Si no hay results, muestra «Sin coincidencias».
Ver solución
const lista = document.querySelector("#lista");
const url =
"https://geocoding-api.open-meteo.com/v1/search" +
"?name=Madrid&language=es&count=1";
fetch(url)
.then((r) => r.json())
.then((data) => {
if (!data.results) {
lista.innerHTML = "<li>Sin coincidencias</li>";
return;
}
const lugar = data.results[0];
lista.innerHTML = `
<li>Nombre: ${lugar.name}</li>
<li>Latitud: ${lugar.latitude}</li>
<li>Longitud: ${lugar.longitude}</li>`;
});10. tiempo-de.html — Generaliza el ejercicio
9: una función coordenadasDe(nombre) que devuelve las coordenadas de una
ciudad, y otra tiempoEn(lat, lon) que devuelve
current_weather. Únelas con .then para pintar el tiempo de
Sevilla.
Ver solución
const coordenadasDe = async (nombre) => {
const r = await fetch(
`https://geocoding-api.open-meteo.com/v1/search` +
`?name=${nombre}&language=es&count=1`
);
const data = await r.json();
if (!data.results) throw new Error("Sin resultados");
return data.results[0];
};
const tiempoEn = async (lat, lon) => {
const r = await fetch(
`https://api.open-meteo.com/v1/forecast` +
`?latitude=${lat}&longitude=${lon}¤t_weather=true`
);
const data = await r.json();
return data.current_weather;
};
coordenadasDe("Sevilla")
.then(({ latitude, longitude }) => tiempoEn(latitude, longitude))
.then((tiempo) => console.log(tiempo.temperature, "ºC"))
.catch((err) => console.log(err.message));Sección D · async/await e integración
11. async-basico.html — Reescribe el
ejercicio 7 con async/await y try/catch: misma información,
mitad de ruido.
Ver solución
const main = async () => {
try {
const r = await fetch(url);
if (!r.ok) throw new Error("HTTP " + r.status);
const data = await r.json();
console.log(data.current_weather);
} catch (error) {
console.log("Fallo de red:", error.message);
}
};
main();12. varias-ciudades.html — Con
Promise.all, pide EN PARALELO el tiempo de Valencia, Madrid y Barcelona
(coords fijas) y pinta las tres temperaturas ordenadas de mayor a menor
(.sort() del Bloque 5).
Ver solución
const ciudades = [
{ nombre: "Valencia", lat: 39.47, lon: -0.38 },
{ nombre: "Madrid", lat: 40.42, lon: -3.70 },
{ nombre: "Barcelona", lat: 41.39, lon: 2.17 },
];
const pedir = ({ lat, lon }) =>
fetch(
`https://api.open-meteo.com/v1/forecast` +
`?latitude=${lat}&longitude=${lon}¤t_weather=true`
).then((r) => r.json());
const main = async () => {
const datos = await Promise.all(ciudades.map(pedir));
const ranking = ciudades
.map(({ nombre }, i) =>
({ nombre, temp: datos[i].current_weather.temperature }))
.sort((a, b) => b.temp - a.temp);
ranking.forEach(({ nombre, temp }) =>
console.log(`${nombre}: ${temp} ºC`));
};
main();13. buscador-lista.html — Un input y un
botón. Al pulsar, busca la ciudad escrita en el geocoder con
count=5 y pinta los cinco resultados (nombre + región + país) usando
map + join sobre innerHTML. Vacío o sin resultados → aviso
visible, jamás alert.
Ver solución
const campo = document.querySelector("#ciudad");
const boton = document.querySelector("#buscar");
const lista = document.querySelector("#resultados");
const aviso = document.querySelector("#aviso");
boton.addEventListener("click", async () => {
const texto = campo.value.trim();
aviso.textContent = "";
if (!texto) {
aviso.textContent = "Escribe una ciudad";
return;
}
try {
const r = await fetch(
`https://geocoding-api.open-meteo.com/v1/search` +
`?name=${encodeURIComponent(texto)}&language=es&count=5`
);
const data = await r.json();
if (!data.results) {
aviso.textContent = "Sin coincidencias";
lista.innerHTML = "";
return;
}
lista.innerHTML = data.results
.map((l) =>
`<li>${l.name} — ${l.admin1 ?? ""} (${l.country})</li>`)
.join("");
} catch {
aviso.textContent = "Error de conexión";
}
});14. 🏆 RETO INTEGRADOR — app-del-tiempo.html Tu primera app conectada a internet. Requisitos:
- Input de ciudad + botón «Buscar» + zona de aviso + lista vacía de resultados.
- Mientras carga, muestra «Buscando…»; al terminar, lo borra.
- Flujo completo: geocodificar la ciudad → pedir current_weather → pintar temperatura, viento y weathercode.
- Errores amables y visibles: campo vacío, ciudad inexistente y fallo de red.
- Todo con async/await + try/catch y
addEventListener. Prohibidoalert. - Bonus: usa
Promise.allpara mostrar también otras dos ciudades favoritas debajo.
Ver una solución posible
<input id="ciudad"><button id="buscar">Buscar</button>
<p id="aviso"></p>
<ul id="panel"></ul>
<script>
const entrada = document.querySelector("#ciudad");
const boton = document.querySelector("#buscar");
const aviso = document.querySelector("#aviso");
const panel = document.querySelector("#panel");
const GEO = "https://geocoding-api.open-meteo.com/v1/search";
const TIEMPO = "https://api.open-meteo.com/v1/forecast";
boton.addEventListener("click", async () => {
const ciudad = entrada.value.trim();
panel.innerHTML = "";
aviso.textContent = "";
if (!ciudad) {
aviso.textContent = "Escribe una ciudad";
return;
}
aviso.textContent = "Buscando…";
try {
const geoR = await fetch(
`${GEO}?name=${encodeURIComponent(ciudad)}&language=es&count=1`);
const geoD = await geoR.json();
if (!geoD.results) {
aviso.textContent = "Ciudad no encontrada";
return;
}
const lugar = geoD.results[0];
const tR = await fetch(
`${TIEMPO}?latitude=${lugar.latitude}` +
`&longitude=${lugar.longitude}¤t_weather=true`);
const tD = await tR.json();
const ahora = tD.current_weather;
panel.innerHTML = `
<li><strong>${lugar.name}</strong></li>
<li>🌡 Temperatura: ${ahora.temperature} ºC</li>
<li>💨 Viento: ${ahora.windspeed} km/h</li>
<li>☀️ Weathercode: ${ahora.weathercode}</li>`;
aviso.textContent = "";
} catch (error) {
aviso.textContent = "Error de conexión. Inténtalo de nuevo.";
}
});
</script>📝 Examen del Bloque 7
14 preguntas, una sola respuesta correcta por pregunta. Necesitas 70 % (10/14) para superarlo.
🧪 Parte B · Laboratorio: app-del-tiempo
Abre el laboratorio y construye la app completa: busca la ciudad con el geocoder de
Open-Meteo, pide su tiempo actual con fetch, maneja los errores con
try/catch y pinta el resultado. Marca cada avance: