Fetch API
Clase 4 de 513 min de lectura
Hasta ahora las promesas las fabricabas tú con un setTimeout para simular esperas. Se acabó simular: con la Fetch API tu JavaScript va a pedir datos de verdad a servidores de verdad.
Y llega en el momento justo, porque fetch devuelve Promises. Todo lo que has aprendido en las clases anteriores lo vas a usar aquí.
¿Qué es la Fetch API?#
Fetch es una API que viene incluida en el navegador para hacer peticiones HTTP de forma sencilla. Está basada por completo en Promises.
¿Para qué sirve Fetch?
- Comunicarte con APIs y obtener datos de un servidor.
- Enviar información al servidor, como un formulario.
- Descargar archivos.
- Actualizar el contenido de una página sin recargarla.
Sintaxis básica
Piensa en fetch como en un mensajero. Le das una dirección (la URL) y te trae lo que encuentre allí. Mientras va y vuelve no te quedas esperando en la puerta: trabaja de forma asíncrona y te avisa cuando tiene la respuesta.
fetch(url, opciones) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error))Traducido al idioma del mensajero:
fetch(url): «ve a esta dirección»..then(response => ...): «cuando vuelvas, dame lo que has encontrado»..json(): «y tradúcemelo a un formato que pueda entender»..then(data => ...): «cuando esté traducido, lo uso»..catch(error => ...): «si algo sale mal, avísame».
Tu primera petición con fetch#
Vamos a hacer una petición HTTP real a una API.
Usaremos JSONPlaceholder, un «servidor de mentira» pensado para practicar. Su dirección es https://jsonplaceholder.typicode.com y, según la ruta que pidas, te devuelve unos datos u otros.
Para recuperar el post número 1, la ruta es https://jsonplaceholder.typicode.com/posts/1:
fetch('https://jsonplaceholder.typicode.com/posts/1') .then(response => { console.log('✅ Respuesta recibida:', response.status) return response.json() // Convertir a JSON }) .then(data => { console.log('📄 Datos obtenidos:') console.log('Título:', data.title) console.log('Contenido:', data.body) }) .catch(error => { console.log('❌ Error:', error) })Repasemos qué hace cada bloque:
- La llamada a
fetchpide el post número 1. - El primer
.then()recibe la respuesta, muestra su código de estado (response.status) y pide convertirla a JSON. - El segundo
.then()recibe los datos ya convertidos y los muestra. - El
.catch()captura cualquier error que ocurra por el camino.
¿Por qué dos .then()? Porque hay dos esperas. La petición devuelve una promesa, y convertir la respuesta con response.json() devuelve otra. Es el encadenamiento de promesas que ya conoces: cada paso espera al anterior.
El Objeto Response: Tu Inspector de Paquetes 📋#
Un detalle importante: cuando el servidor responde, fetch no te entrega los datos directamente. Te entrega un objeto Response con toda la información sobre la respuesta.
Es como recibir un paquete: antes de abrirlo, le echas un vistazo por fuera. Y el Response te deja comprobar justo eso:
- ¿Con qué código ha respondido el servidor? (
status) - ¿Ha ido todo bien? (
ok) - ¿De dónde viene exactamente? (
url) - ¿Qué tipo de contenido trae? (
headers)
En este ejemplo inspeccionamos el paquete y solo lo abrimos si response.ok es true:
fetch('https://jsonplaceholder.typicode.com/users/1') .then(response => { console.log('📊 Información de la respuesta:') console.log('Estado:', response.status) // 200, 404, 500, etc. console.log('OK:', response.ok) // true si status 200-299 console.log('URL:', response.url) console.log('Headers:', response.headers) // Verificar si la respuesta es exitosa if (response.ok) { return response.json() } else { throw new Error(`Error HTTP: ${response.status}`) } }) .then(user => { console.log('👤 Usuario:', user.name) console.log('📧 Email:', user.email) }) .catch(error => { console.log('❌ Error:', error.message) })Si la respuesta no es buena, lanzamos un error con throw. Al estar dentro de un .then(), ese error rechaza la cadena y acaba en el .catch().
fetch(url) .then(response => { if (!response.ok) throw new Error(response.status) return response.json() }) .then(data => console.log(data.title)) .catch(error => console.error(error)).then() recibe los datos ya convertidos: ahora sí puedes usar data.title.Métodos del objeto Response: las diferentes formas de «abrir el paquete»
Según el tipo de contenido que esperes, abrirás el paquete con una herramienta u otra:
| Método | Qué te da | Cuándo usarlo |
|---|---|---|
.json() | Un objeto JavaScript | Datos estructurados (lo habitual en las APIs) |
.text() | Texto plano | HTML, CSS, texto simple |
.blob() | Un archivo binario | Imágenes, vídeos, archivos |
.arrayBuffer() | Datos binarios en crudo | Manipulación avanzada de archivos |
.formData() | Datos de formulario | Formularios HTML |
Todos ellos devuelven una Promise, por eso hace falta un segundo .then() para recibir el contenido.
Tipos de Peticiones HTTP#
HTTP es el idioma en el que hablan navegadores y servidores, y cada tipo de petición (o método) expresa una intención distinta. Como las frases que le dices a un camarero:
| Método | En el restaurante | Qué hace |
|---|---|---|
GET | «¿Qué hay en el menú?» | Leer datos |
POST | «Quiero pedir esto» | Crear algo nuevo |
PUT | «Cambia todo mi pedido por este otro» | Reemplazar por completo |
PATCH | «Solo cambia las papas por ensalada» | Modificar una parte |
DELETE | «Cancela mi pedido» | Eliminar |
GET - Obtener Datos
Es el método más común y el que usa fetch si no le dices otra cosa. Solo lee información: no modifica nada en el servidor.
// Obtener lista de usuariosfetch('https://jsonplaceholder.typicode.com/users') .then(response => { if (!response.ok) { throw new Error(`Error: ${response.status}`) } return response.json() }) .then(usuarios => { console.log('👥 Usuarios obtenidos:', usuarios.length) usuarios.slice(0, 3).forEach(user => { console.log(`- ${user.name} (${user.email})`) }) }) .catch(error => { console.log('❌ Error obteniendo usuarios:', error.message) })Pedimos la lista completa de usuarios y mostramos el nombre y el email de los tres primeros.
El segundo parámetro de fetch: Configurar la petición
Para todo lo que no sea un GET sencillo necesitas el segundo parámetro de fetch: un objeto con la configuración de la petición. En él puedes indicar tres cosas:
method: el método HTTP (por defecto,GET).headers: cabeceras con información extra, como el formato de los datos que envías.body: los datos que quieres mandar al servidor.
fetch('https://jsonplaceholder.typicode.com/posts', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: JSON.stringify({ title: 'Mi primer post con Fetch', body: 'Este es el contenido de mi post creado con JavaScript', userId: 1 })})Fíjate en el JSON.stringify(): el body viaja como texto, así que hay que convertir el objeto a una cadena JSON. Y con la cabecera Content-Type le avisas al servidor de que lo que llega es JSON.
POST - Enviar Datos
POST sirve para crear recursos nuevos en el servidor: registrar un usuario, publicar un post, enviar un formulario o subir un archivo.
// Crear un nuevo postfunction crearPost() { const nuevoPost = { title: 'Mi primer post con Fetch', body: 'Este es el contenido de mi post creado con JavaScript', userId: 1 } return fetch('https://jsonplaceholder.typicode.com/posts', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(nuevoPost) }) .then(response => { if (!response.ok) { throw new Error(`Error: ${response.status}`) } return response.json() }) .then(postCreado => { console.log('✅ Post creado exitosamente:') console.log('ID:', postCreado.id) console.log('Título:', postCreado.title) }) .catch(error => { console.log('❌ Error creando post:', error.message) })}crearPost()El servidor responde con el post ya creado, incluido el id que le ha asignado.
PUT - Actualizar Completamente
PUT reemplaza por completo un recurso que ya existe. Es como reformar una casa tirándola abajo y construyéndola de nuevo: tienes que enviar el recurso entero, no solo lo que cambia.
// Actualizar un post completofunction actualizarPost(id) { const postActualizado = { id: id, title: 'Post actualizado con PUT', body: 'Este contenido ha sido completamente reemplazado', userId: 1 } return fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(postActualizado) }) .then(response => { if (!response.ok) { throw new Error(`Error: ${response.status}`) } return response.json() }) .then(postActualizado => { console.log('🔄 Post actualizado:', postActualizado.title) }) .catch(error => { console.log('❌ Error actualizando:', error.message) })}actualizarPost(1)PATCH - Actualización parcial
PATCH es el cirujano: toca solo la parte que hay que cambiar y deja el resto como estaba. Envías únicamente los campos que quieres modificar, en este caso el título:
// Actualizar solo el títulofunction actualizarTitulo(id, nuevoTitulo) { return fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title: nuevoTitulo }) }) .then(response => { if (!response.ok) { throw new Error(`Error: ${response.status}`) } return response.json() }) .then(resultado => { console.log('📝 Título actualizado:', resultado.title) return resultado }) .catch(error => { console.log('❌ Error:', error.message) })}actualizarTitulo(1, 'Nuevo título con PATCH')DELETE - Eliminar el recurso
DELETE elimina un recurso del servidor. Como no hay datos que enviar, basta con indicar el método:
// Eliminar un postfunction eliminarPost(id) { return fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, { method: 'DELETE' }).then(response => { if (!response.ok) { throw new Error(`Error: ${response.status}`) } // DELETE generalmente no devuelve contenido útil if (response.status === 200) { console.log(`🗑️ Post ${id} eliminado exitosamente`) } return response }) .catch(error => { console.log('❌ Error:', error.message) })}eliminarPost(1)Manejo de Errores Avanzado: Conviértete en un Detective de Problemas 🕵️♀️#
Las peticiones fallan. La red se cae, el servidor tiene un mal día, la URL está mal escrita. No puedes evitarlo, pero sí decidir qué hace tu código cuando ocurre.
Verificación de estados HTTP: tu sistema de alarmas
Los códigos de estado HTTP se agrupan por su primera cifra, y funcionan como un semáforo:
| Códigos | Significado |
|---|---|
| 2xx (200-299) | 🟢 Todo ha ido bien |
| 3xx (300-399) | 🟡 Redirección: lo que buscas está en otro sitio |
| 4xx (400-499) | 🔴 El error es tuyo (de quien hace la petición) |
| 5xx (500-599) | 💥 El error es del servidor |
En esta función comprobamos response.ok y, si algo va mal, lanzamos un error distinto según el código:
function peticionConManejoDeErrores(url) { return fetch(url) .then(response => { // Fetch NO rechaza la promesa para códigos 4xx o 5xx // Pero sí puedes verificar si todo salió bien con response.ok if (!response.ok) { // Manejar diferentes tipos de error switch (response.status) { case 404: throw new Error('🔍 Recurso no encontrado') case 401: throw new Error('🔐 No autorizado') case 403: throw new Error('🚫 Acceso prohibido') case 500: throw new Error('💥 Error interno del servidor') default: throw new Error(`❌ Error HTTP: ${response.status}`) } } return response.json() }) .then(data => { console.log('✅ Datos obtenidos:', data) return data }) .catch(error => { if (error.name === 'TypeError') { console.log('🌐 Error de conexión:', error.message) } else { console.log('Error:', error.message) } })}// Probar con URL que no existepeticionConManejoDeErrores('https://jsonplaceholder.typicode.com/posts/999999')Al .catch() llegan dos tipos de problema. Los que has lanzado tú con throw y los fallos de red, que son los únicos por los que fetch rechaza la promesa por su cuenta y que llegan como un TypeError.
¡Últimas palabras para fetch#
Con fetch ya puedes hablar con cualquier servidor. Esta es la chuleta de los métodos HTTP que has usado:
| Método | Para qué |
|---|---|
GET | Obtener datos |
POST | Crear recursos |
PUT | Reemplazar un recurso completo |
PATCH | Modificar parte de un recurso |
DELETE | Eliminar recursos |
Próximo paso
Todas estas cadenas de .then() funcionan, pero se pueden escribir de forma más limpia. En la siguiente clase verás async/await, que hace que trabajar con fetch sea todavía más fácil de leer.
En resumen
fetch(url, opciones)hace peticiones HTTP y devuelve una Promise.- La respuesta es un objeto
Response: para leer los datos hay que llamar a.json(),.text()..., que devuelven otra promesa. - El segundo parámetro configura el método, las cabeceras y el cuerpo de la petición.
fetchno rechaza la promesa con códigos 4xx o 5xx: comprueba siempreresponse.ok.