Saltar al contenido principal
09Programación Asíncrona

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:

  1. fetch(url): «ve a esta dirección».
  2. .then(response => ...): «cuando vuelvas, dame lo que has encontrado».
  3. .json(): «y tradúcemelo a un formato que pueda entender».
  4. .then(data => ...): «cuando esté traducido, lo uso».
  5. .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 fetch pide 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().

Infografía
Viaje de una petición con fetch
5.El segundo .then() recibe los datos ya convertidos: ahora sí puedes usar data.title.
Paso 5 de 5

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étodoQué te daCuándo usarlo
.json()Un objeto JavaScriptDatos estructurados (lo habitual en las APIs)
.text()Texto planoHTML, CSS, texto simple
.blob()Un archivo binarioImágenes, vídeos, archivos
.arrayBuffer()Datos binarios en crudoManipulación avanzada de archivos
.formData()Datos de formularioFormularios 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étodoEn el restauranteQué 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ódigosSignificado
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étodoPara qué
GETObtener datos
POSTCrear recursos
PUTReemplazar un recurso completo
PATCHModificar parte de un recurso
DELETEEliminar 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.
  • fetch no rechaza la promesa con códigos 4xx o 5xx: comprueba siempre response.ok.