Fetch API y async/await
Fetch permite hacer peticiones HTTP desde el navegador sin recargar la página. Con async/await el código asincrónico se lee casi igual que el sincrónico.
¿Qué es una petición HTTP?
Cuando el navegador carga una página, hace una petición HTTP al servidor. Con Fetch podés hacer esas mismas peticiones desde JavaScript — para traer datos, enviar formularios, o comunicarte con una API — sin que la página se recargue.
// GET básico — traer datos de una URL
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(data => {
console.log(data.title);
})
.catch(error => {
console.error("Error:", error);
});
Una API (Application Programming Interface) es un servicio que devuelve datos en formato JSON. En estos ejemplos usamos JSONPlaceholder — una API pública de prueba con posts, usuarios, comentarios y más.
async / await — la forma moderna
En vez de encadenar .then(), podés usar async/await para escribir
código asincrónico de forma lineal y más legible. Una función async siempre
devuelve una Promise.
// Con .then() — encadenado
fetch(url)
.then(res => res.json())
.then(data => mostrar(data))
.catch(err => console.error(err));
// Con async/await — equivalente, más legible
async function cargarDatos() {
try {
const res = await fetch(url);
const data = await res.json();
mostrar(data);
} catch (error) {
console.error("Error:", error);
}
}
cargarDatos();
await solo puede usarse dentro de una función marcada como async. Si estás en el nivel superior del módulo (top-level), podés usar await directamente en navegadores modernos.
Verificar errores HTTP
fetch() solo lanza un error de red si la conexión falla. Un status
404 o 500 no dispara el catch — hay que verificarlo manualmente
con response.ok.
async function cargarUsuario(id) {
try {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
// response.ok es true para status 200-299
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const usuario = await res.json();
return usuario;
} catch (error) {
console.error("Falló la petición:", error.message);
return null;
}
}
// Llamar la función
const usuario = await cargarUsuario(1);
if (usuario) {
console.log(usuario.name);
}
| Propiedad | Descripción | Ejemplo |
|---|---|---|
response.ok | true si status 200–299 | if (!res.ok) throw... |
response.status | Código HTTP numérico | 200, 404, 500 |
response.statusText | Mensaje del status | "OK", "Not Found" |
response.json() | Parsea el body como JSON | devuelve una Promise |
response.text() | Body como string plano | devuelve una Promise |
POST — enviar datos
Para enviar datos al servidor usás el método POST y pasás el body como JSON.
No olvidés el header Content-Type.
async function crearPost(titulo, cuerpo, userId) {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: titulo,
body: cuerpo,
userId: userId,
}),
});
if (!res.ok) throw new Error(`Error ${res.status}`);
const nuevoPost = await res.json();
console.log("Post creado con ID:", nuevoPost.id);
return nuevoPost;
} catch (error) {
console.error("No se pudo crear:", error.message);
}
}
crearPost("Mi título", "Contenido del post", 1);
JSON.stringify(obj) convierte un objeto JavaScript a texto JSON para enviarlo. JSON.parse(texto) hace lo inverso. response.json() llama a parse internamente.
Múltiples peticiones en paralelo
Si necesitás hacer varias peticiones independientes, Promise.all() las lanza
en paralelo y espera a que todas terminen — mucho más rápido que hacerlas una por una.
// ❌ Secuencial — espera una antes de empezar la otra
const usuarios = await fetch(url1).then(r => r.json());
const posts = await fetch(url2).then(r => r.json());
// tarda: tiempo(usuarios) + tiempo(posts)
// ✅ Paralelo — las dos peticiones van al mismo tiempo
const [usuarios, posts] = await Promise.all([
fetch(url1).then(r => r.json()),
fetch(url2).then(r => r.json()),
]);
// tarda: max(tiempo(usuarios), tiempo(posts))
// Promise.allSettled — continúa aunque alguna falle
const resultados = await Promise.allSettled([
fetch(url1).then(r => r.json()),
fetch(url2).then(r => r.json()),
fetch(url3).then(r => r.json()),
]);
resultados.forEach((resultado, i) => {
if (resultado.status === "fulfilled") {
console.log(`Petición ${i}: OK`, resultado.value);
} else {
console.log(`Petición ${i}: Error`, resultado.reason);
}
});
Probá el Ejemplo 3: Peticiones en paralelo
Estados de carga — loading, error, datos
Una buena UX siempre muestra feedback: un indicador mientras carga, el resultado si tuvo éxito, y un mensaje claro si hubo un error. Este patrón se repite en cualquier app que use fetch.
const contenedor = document.getElementById("contenido");
function mostrarLoading() {
contenedor.innerHTML = `
<div class="loading">
<span class="spinner"></span> Cargando...
</div>`;
}
function mostrarError(mensaje) {
contenedor.innerHTML = `
<div class="error">
❌ ${mensaje}
<button onclick="cargar()">Reintentar</button>
</div>`;
}
function mostrarDatos(datos) {
contenedor.innerHTML = datos
.map(item => `<div class="item">${item.title}</div>`)
.join("");
}
async function cargar() {
mostrarLoading();
try {
const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");
if (!res.ok) throw new Error(`Error ${res.status}`);
const datos = await res.json();
mostrarDatos(datos);
} catch (error) {
mostrarError(error.message);
}
}
cargar();
Probá el Ejemplo 4: App completa con fetch
Abortar una petición
Con AbortController podés cancelar una petición en curso — útil para búsquedas
en tiempo real donde cada keystroke lanza una nueva petición.
let controller = null;
async function buscar(termino) {
// Cancelar la petición anterior si todavía está en curso
if (controller) controller.abort();
controller = new AbortController();
try {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts?q=${termino}`,
{ signal: controller.signal }
);
const datos = await res.json();
mostrar(datos);
} catch (error) {
if (error.name === "AbortError") return; // fue cancelada — ignorar
console.error("Error de red:", error);
}
}
// Buscar al escribir, cancelando la petición anterior
input.addEventListener("input", (e) => {
buscar(e.target.value);
});
Métodos HTTP más usados
| Método | Acción | Tiene body | Uso típico |
|---|---|---|---|
GET | Leer datos | No | Cargar lista, perfil, búsqueda |
POST | Crear recurso | ✅ Sí | Registrar usuario, crear post |
PUT | Reemplazar recurso | ✅ Sí | Editar perfil completo |
PATCH | Modificar parcialmente | ✅ Sí | Cambiar solo el email |
DELETE | Eliminar recurso | No | Borrar comentario, cuenta |
Buenas Prácticas
- Siempre verificá response.ok: fetch no lanza error en 404 o 500 — tenés que controlarlo vos
- Siempre usá try/catch: los errores de red sí lanzan excepciones
- async/await sobre .then(): más legible, más fácil de debuggear
- Promise.all() para peticiones paralelas: no las hagas secuenciales si no dependen una de la otra
- AbortController para búsquedas: evita condiciones de carrera cuando el usuario escribe rápido
- Mostrá estados de carga y error: el usuario siempre tiene que saber qué está pasando
- Nunca pongas API keys en el frontend: el código JS es visible para cualquiera — las keys van en el servidor