Fetch API en JavaScript: guía Completa con Ejemplos prácticos
Aprende a usar la Fetch API de JavaScript desde cero. GET, POST, headers, errores y todo lo que necesitas saber con ejemplos reales.
Fetch API en JavaScript: guía Completa
La Fetch API en JavaScript es la forma nativa y moderna de hacer peticiones HTTP desde el navegador y Node.js. Si necesitas cargar datos de una API, enviar información a un servidor, o subir archivos, fetch es tu herramienta principal.
¿Qué es fetch?
fetch es una función integrada en JavaScript que te permite hacer peticiones HTTP a servidores. Te permite comunicarte con APIs y obtener o enviar información sin depender de librerías externas.
¿Por qué usar fetch?
Antes de fetch, usabamos XMLHttpRequest (una API antigua y complicada):
// Forma antigua con XMLHttpRequest (NO uses esto)
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://api.ejemplo.com/usuarios')
xhr.onload = function() {
if (xhr.status === 200) {
const datos = JSON.parse(xhr.responseText)
console.log(datos)
}
}
xhr.onerror = function() {
console.error('Error')
}
xhr.send()Complicado, verdad?
Con fetch (forma moderna):
fetch('https://api.ejemplo.com/usuarios')
.then(response => response.json())
.then(datos => console.log(datos))
.catch(error => console.error('Error:', error))Mucho más limpio y fácil de entender.
Disponibilidad
Fetch esta disponible en todos los navegadores modernos y en Node.js desde la versión 18. Es el estandar actual para hacer peticiones HTTP.
Sintaxis básica de fetch
La forma más simple de usar fetch:
fetch(url)Esto retorna una Promise (promesa) que se resuelve con la respuesta del servidor. Si no tienes claro como funcionan las promesas y async/await, revisa la guía de async/await en JavaScript.
Ejemplo básico con async/await
async function obtenerDatos() {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const datos = await response.json()
console.log(datos)
}
obtenerDatos()Desglosando el código:
fetch(url)- Hace la petición al servidorawait- Espera a que llegue la respuestaresponse.json()- Convierte la respuesta a formato JSON- Los datos estan listos para usar
Ejemplo básico con .then()
Si prefieres usar Promises directamente:
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(datos => {
console.log(datos)
})
.catch(error => {
console.error('Error:', error)
})Ambas formas funcionan. Async/await es generalmente más fácil de leer.
El objeto Response
Cuando haces un fetch, obtienes un objeto Response con información sobre la respuesta del servidor:
async function verificarRespuesta() {
const response = await fetch('https://api.ejemplo.com/datos')
console.log('Status:', response.status) // 200, 404, 500, etc.
console.log('OK:', response.ok) // true si status 200-299
console.log('Status Text:', response.statusText) // "OK", "Not Found", etc.
console.log('Headers:', response.headers) // Headers de la respuesta
console.log('URL:', response.url) // URL completa
}Propiedades importantes:
| Propiedad | Descripción | Ejemplo |
|---|---|---|
response.ok | true si status 200-299 | true o false |
response.status | código de estado HTTP | 200, 404, 500 |
response.statusText | Texto del estado | "OK", "Not Found" |
response.headers | Headers de respuesta | Objeto Headers |
response.body | Cuerpo de la respuesta | ReadableStream |
métodos HTTP: GET, POST, PUT, DELETE
HTTP tiene diferentes "verbos" o métodos para diferentes acciones:
| método | propósito | Analogia |
|---|---|---|
| GET | Obtener datos | Leer un libro |
| POST | Crear datos nuevos | Escribir un libro nuevo |
| PUT | Actualizar datos existentes | Editar un libro completo |
| PATCH | Actualizar parte de datos | Editar una página del libro |
| DELETE | Eliminar datos | Tirar el libro |
GET: Obtener datos
GET es el método por defecto de fetch. Se usa para obtener información:
// Sintaxis simple (método GET por defecto)
async function obtenerUsuarios() {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const usuarios = await response.json()
console.log(usuarios)
}
// Sintaxis explicita
async function obtenerUsuarios() {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'GET'
})
const usuarios = await response.json()
console.log(usuarios)
}Ejemplo real: Obtener un usuario específico
async function obtenerUsuario(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
if (!response.ok) {
throw new Error('Usuario no encontrado')
}
const usuario = await response.json()
return usuario
}
// Usar la función
obtenerUsuario(1)
.then(usuario => console.log(usuario.name))
.catch(error => console.error('Error:', error))POST: Crear datos nuevos
POST se usa para enviar datos al servidor y crear recursos nuevos:
async function crearUsuario(datosUsuario) {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(datosUsuario)
})
const usuarioCreado = await response.json()
return usuarioCreado
}
// Usar la función
const nuevoUsuario = {
name: 'Ana Garcia',
email: 'ana@ejemplo.com',
username: 'anagarcia'
}
crearUsuario(nuevoUsuario)
.then(usuario => console.log('Usuario creado:', usuario))
.catch(error => console.error('Error:', error))Elementos importantes de POST:
method: 'POST'- específica el métodoheaders- Le dice al servidor que tipo de datos enviasbody- Los datos que envias (convertidos a string con JSON.stringify)
PUT: Actualizar datos completos
PUT se usa para actualizar un recurso completo:
async function actualizarUsuario(id, datosActualizados) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(datosActualizados)
})
const usuarioActualizado = await response.json()
return usuarioActualizado
}
// Usar la función
const datosActualizados = {
id: 1,
name: 'Ana Garcia Actualizada',
email: 'ana.nueva@ejemplo.com',
username: 'anagarcia'
}
actualizarUsuario(1, datosActualizados)
.then(usuario => console.log('Usuario actualizado:', usuario))PATCH: Actualizar datos parciales
PATCH se usa para actualizar solo algunos campos:
async function actualizarEmail(id, nuevoEmail) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: nuevoEmail // Solo actualizar el email
})
})
const usuarioActualizado = await response.json()
return usuarioActualizado
}
// Usar la función
actualizarEmail(1, 'nuevo@ejemplo.com')
.then(usuario => console.log('Email actualizado:', usuario))Diferencia entre PUT y PATCH:
- PUT: Reemplaza el recurso completo (debes enviar todos los campos)
- PATCH: Actualiza solo los campos que especifiques
DELETE: Eliminar datos
DELETE se usa para eliminar recursos:
async function eliminarUsuario(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: 'DELETE'
})
if (response.ok) {
console.log('Usuario eliminado exitosamente')
}
}
// Usar la función
eliminarUsuario(1)
.then(() => console.log('Usuario eliminado'))
.catch(error => console.error('Error:', error))Headers (Encabezados)
Los headers son información adicional que envias con tu petición. Piensalos como el sobre de una carta que contiene datos sobre el remitente, destinatario, etc.
Headers comunes
const response = await fetch('https://api.ejemplo.com/datos', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // Tipo de datos que envias
'Accept': 'application/json', // Tipo de datos que aceptas
'Authorization': 'Bearer tu-token-aquí', // Token de autenticación
'X-Custom-Header': 'valor-personalizado' // Header personalizado
},
body: JSON.stringify({ dato: 'valor' })
})Headers más usados:
| Header | propósito | Ejemplo |
|---|---|---|
Content-Type | Tipo de datos que envias | application/json |
Accept | Tipo de datos que aceptas | application/json |
Authorization | Token de autenticación | Bearer abc123... |
User-Agent | Información del navegador | Mozilla/5.0... |
Cuando trabajas con tokens de autenticación y API keys en tus headers, Asegúrate de no exponerlos en tu repositorio. Herramientas como datahogo escanean tu repo y detectan credenciales expuestas automáticamente.
Leer headers de la respuesta
async function verHeaders() {
const response = await fetch('https://api.ejemplo.com/datos')
// Obtener un header específico
const contentType = response.headers.get('content-type')
console.log('Content-Type:', contentType)
// Iterar sobre todos los headers
response.headers.forEach((valor, nombre) => {
console.log(`${nombre}: ${valor}`)
})
}Diferentes tipos de datos
JSON (lo más común)
async function enviarJSON() {
const response = await fetch('https://api.ejemplo.com/usuarios', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
nombre: 'Ana',
edad: 25
})
})
const datos = await response.json()
console.log(datos)
}Texto plano
async function obtenerTexto() {
const response = await fetch('https://ejemplo.com/archivo.txt')
const texto = await response.text()
console.log(texto)
}FormData (para formularios y archivos)
async function subirArchivo(archivo) {
const formData = new FormData()
formData.append('archivo', archivo)
formData.append('descripción', 'Mi archivo')
const response = await fetch('https://api.ejemplo.com/upload', {
method: 'POST',
body: formData // NO pongas Content-Type, se añade automáticamente
})
const resultado = await response.json()
console.log(resultado)
}
// Usar con un input de archivo
const input = document.querySelector('input[type="file"]')
input.addEventListener('change', (e) => {
const archivo = e.target.files[0]
subirArchivo(archivo)
})Blob (para imágenes, videos, etc.)
async function descargarImagen() {
const response = await fetch('https://ejemplo.com/imagen.jpg')
const blob = await response.blob()
// Crear URL temporal para la imagen
const url = URL.createObjectURL(blob)
// Mostrar la imagen
const img = document.createElement('img')
img.src = url
document.body.appendChild(img)
}Manejo de errores
Fetch tiene una peculiaridad: solo rechaza la promesa en errores de red. Si el servidor responde con error 404 o 500, fetch NO lo considera error.
Verificar response.ok
async function obtenerDatos() {
try {
const response = await fetch('https://api.ejemplo.com/datos')
// Verificar si la respuesta fue exitosa
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const datos = await response.json()
return datos
} catch (error) {
console.error('Error:', error.message)
throw error
}
}Manejo completo de errores
async function obtenerUsuario(id) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
// Error 404, 500, etc.
if (!response.ok) {
if (response.status === 404) {
throw new Error('Usuario no encontrado')
} else if (response.status === 500) {
throw new Error('Error del servidor')
} else {
throw new Error(`Error: ${response.status}`)
}
}
const usuario = await response.json()
return usuario
} catch (error) {
// Errores de red (sin internet, servidor caido, etc.)
if (error instanceof TypeError) {
console.error('Error de red:', error.message)
throw new Error('No se pudo conectar al servidor')
}
// Otros errores
console.error('Error:', error.message)
throw error
}
}
// Usar la función
obtenerUsuario(999)
.then(usuario => console.log(usuario))
.catch(error => console.error('Fallo:', error.message))Fetch no rechaza en errores HTTP
A diferencia de otras librerías, fetch solo rechaza la promesa en errores de red (sin internet, DNS fallo, etc.). Respuestas con status 404, 500, etc. se consideran "exitosas" y debes verificar response.ok manualmente.
Opciones de configuración
Fetch acepta un segundo parámetro con opciones:
const response = await fetch(url, {
method: 'GET', // GET, POST, PUT, DELETE, etc.
headers: {}, // Headers personalizados
body: null, // Datos a enviar (no en GET)
mode: 'cors', // cors, no-cors, same-origin
credentials: 'include', // omit, same-origin, include
cache: 'default', // default, no-cache, reload, force-cache
redirect: 'follow', // follow, error, manual
referrer: 'client', // URL del referrer
signal: null // AbortSignal para cancelar
})Opciones comunes explicadas
mode:
// cors: Permite peticiones a otros dominios (lo más común)
fetch('https://api.ejemplo.com/datos', {
mode: 'cors'
})
// same-origin: Solo permite peticiones al mismo dominio
fetch('/api/datos', {
mode: 'same-origin'
})credentials:
// include: Envia cookies incluso a otros dominios
fetch('https://api.ejemplo.com/datos', {
credentials: 'include'
})
// same-origin: Solo envia cookies al mismo dominio (default)
fetch('/api/datos', {
credentials: 'same-origin'
})
// omit: No envia cookies
fetch('https://api.ejemplo.com/datos', {
credentials: 'omit'
})cache:
// no-cache: Siempre pide datos frescos
fetch('https://api.ejemplo.com/datos', {
cache: 'no-cache'
})
// force-cache: Usa cache si esta disponible
fetch('https://api.ejemplo.com/datos', {
cache: 'force-cache'
})Cancelar peticiones con AbortController
A veces necesitas cancelar una petición (usuario cambia de página, timeout, etc.):
async function buscarConTimeout() {
// Crear un controlador para cancelar
const controller = new AbortController()
const signal = controller.signal
// Cancelar después de 5 segundos
const timeout = setTimeout(() => controller.abort(), 5000)
try {
const response = await fetch('https://api.ejemplo.com/datos', {
signal: signal // Pasar el signal
})
clearTimeout(timeout) // Cancelar el timeout si termina a tiempo
const datos = await response.json()
return datos
} catch (error) {
if (error.name === 'AbortError') {
console.log('Petición cancelada')
} else {
console.error('Error:', error)
}
}
}Ejemplo práctico: búsqueda con cancelación
let controladorActual = null
async function buscar(query) {
// Cancelar búsqueda anterior si existe
if (controladorActual) {
controladorActual.abort()
}
// Crear nuevo controlador
controladorActual = new AbortController()
try {
const response = await fetch(`/api/buscar?q=${query}`, {
signal: controladorActual.signal
})
const resultados = await response.json()
mostrarResultados(resultados)
} catch (error) {
if (error.name === 'AbortError') {
console.log('búsqueda cancelada')
}
}
}
// En un input de búsqueda
input.addEventListener('input', (e) => {
buscar(e.target.value)
})Esto cancela búsquedas anteriores si el usuario sigue escribiendo.
Ejemplos prácticos completos
Ejemplo 1: Cargar y mostrar lista de usuarios
async function cargarUsuarios() {
const contenedor = document.getElementById('usuarios')
contenedor.innerHTML = '<p>Cargando...</p>'
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
if (!response.ok) {
throw new Error('Error al cargar usuarios')
}
const usuarios = await response.json()
// Limpiar contenedor
contenedor.innerHTML = ''
// Crear HTML para cada usuario
usuarios.forEach(usuario => {
const div = document.createElement('div')
div.innerHTML = `
<h3>${usuario.name}</h3>
<p>Email: ${usuario.email}</p>
<p>Ciudad: ${usuario.address.city}</p>
<hr>
`
contenedor.appendChild(div)
})
} catch (error) {
contenedor.innerHTML = `<p>Error: ${error.message}</p>`
}
}
// Llamar al cargar la página
cargarUsuarios()Ejemplo 2: Formulario de contacto
async function enviarFormulario(evento) {
evento.preventDefault()
// Obtener datos del formulario
const formulario = evento.target
const datos = {
nombre: formulario.nombre.value,
email: formulario.email.value,
mensaje: formulario.mensaje.value
}
// Deshabilitar boton mientras envia
const boton = formulario.querySelector('button')
boton.disabled = true
boton.textContent = 'Enviando...'
try {
const response = await fetch('/api/contacto', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(datos)
})
if (!response.ok) {
throw new Error('Error al enviar mensaje')
}
const resultado = await response.json()
alert('Mensaje enviado exitosamente')
formulario.reset()
} catch (error) {
alert('Error: ' + error.message)
} finally {
boton.disabled = false
boton.textContent = 'Enviar'
}
}
// Conectar con el formulario
document.getElementById('formulario-contacto')
.addEventListener('submit', enviarFormulario)Ejemplo 3: Subir imagen con preview
async function subirImagen(archivo) {
// Mostrar preview
const preview = document.getElementById('preview')
const reader = new FileReader()
reader.onload = (e) => {
preview.src = e.target.result
}
reader.readAsDataURL(archivo)
// Preparar FormData
const formData = new FormData()
formData.append('imagen', archivo)
formData.append('título', 'Mi imagen')
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
})
if (!response.ok) {
throw new Error('Error al subir imagen')
}
const resultado = await response.json()
console.log('Imagen subida:', resultado.url)
alert('Imagen subida exitosamente')
} catch (error) {
alert('Error: ' + error.message)
}
}
// Conectar con input file
document.getElementById('input-imagen')
.addEventListener('change', (e) => {
const archivo = e.target.files[0]
if (archivo) {
subirImagen(archivo)
}
})Ejemplo 4: CRUD completo
// CRUD = Create, Read, Update, Delete
const API_URL = 'https://jsonplaceholder.typicode.com/users'
// CREATE - Crear usuario
async function crearUsuario(datos) {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(datos)
})
return await response.json()
}
// READ - Leer usuarios
async function obtenerUsuarios() {
const response = await fetch(API_URL)
return await response.json()
}
// READ - Leer un usuario
async function obtenerUsuario(id) {
const response = await fetch(`${API_URL}/${id}`)
return await response.json()
}
// UPDATE - Actualizar usuario
async function actualizarUsuario(id, datos) {
const response = await fetch(`${API_URL}/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(datos)
})
return await response.json()
}
// DELETE - Eliminar usuario
async function eliminarUsuario(id) {
const response = await fetch(`${API_URL}/${id}`, {
method: 'DELETE'
})
return response.ok
}
// Usar las funciones
async function ejemploCRUD() {
// Crear
const nuevoUsuario = await crearUsuario({
name: 'Ana Garcia',
email: 'ana@ejemplo.com'
})
console.log('Usuario creado:', nuevoUsuario)
// Leer todos
const usuarios = await obtenerUsuarios()
console.log('Usuarios:', usuarios.length)
// Leer uno
const usuario = await obtenerUsuario(1)
console.log('Usuario 1:', usuario.name)
// Actualizar
const actualizado = await actualizarUsuario(1, {
name: 'Ana Garcia Actualizada',
email: 'ana.nueva@ejemplo.com'
})
console.log('Usuario actualizado:', actualizado)
// Eliminar
const eliminado = await eliminarUsuario(1)
console.log('Usuario eliminado:', eliminado)
}Patrones útiles
Patron 1: Función wrapper reutilizable
async function fetchJSON(url, opciones = {}) {
try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...opciones.headers
},
...opciones
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.json()
} catch (error) {
console.error('Error en fetch:', error)
throw error
}
}
// Usar la función wrapper
const usuarios = await fetchJSON('https://api.ejemplo.com/usuarios')
const usuario = await fetchJSON('https://api.ejemplo.com/usuarios/1')Patron 2: Retry automático
async function fetchConReintentos(url, opciones = {}, intentos = 3) {
for (let i = 0; i < intentos; i++) {
try {
const response = await fetch(url, opciones)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.json()
} catch (error) {
// Si es el último intento, lanzar el error
if (i === intentos - 1) throw error
// Esperar antes de reintentar (backoff exponencial)
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
}
}
}
// Usar con reintentos
const datos = await fetchConReintentos('https://api.ejemplo.com/datos', {}, 3)Patron 3: Cache simple
const cache = new Map()
async function fetchConCache(url, tiempoCache = 60000) {
// Verificar si esta en cache y no ha expirado
if (cache.has(url)) {
const { datos, timestamp } = cache.get(url)
if (Date.now() - timestamp < tiempoCache) {
console.log('Usando cache')
return datos
}
}
// Hacer fetch y guardar en cache
console.log('Haciendo fetch')
const response = await fetch(url)
const datos = await response.json()
cache.set(url, {
datos,
timestamp: Date.now()
})
return datos
}
// Primera llamada: hace fetch
const datos1 = await fetchConCache('https://api.ejemplo.com/datos')
// Segunda llamada (dentro de 60s): usa cache
const datos2 = await fetchConCache('https://api.ejemplo.com/datos')Patron 4: Progreso de descarga
async function descargarConProgreso(url) {
const response = await fetch(url)
const total = response.headers.get('content-length')
let descargado = 0
const chunks = []
const reader = response.body.getReader()
while (true) {
const { done, value } = await reader.read()
if (done) break
chunks.push(value)
descargado += value.length
// Calcular progreso
const progreso = total ? (descargado / total) * 100 : 0
console.log(`Descargado: ${progreso.toFixed(2)}%`)
}
// Combinar todos los chunks
const blob = new Blob(chunks)
return blob
}
// Usar con una imagen
descargarConProgreso('https://ejemplo.com/imagen-grande.jpg')
.then(blob => {
const url = URL.createObjectURL(blob)
console.log('Descarga completa:', url)
})Errores comunes
Error 1: Olvidar await en .json()
// Incorrecto
async function obtenerDatos() {
const response = await fetch('url')
const datos = response.json() // Falta await
console.log(datos) // Imprime una Promise, no los datos
}
// Correcto
async function obtenerDatos() {
const response = await fetch('url')
const datos = await response.json()
console.log(datos)
}Error 2: No verificar response.ok
// Incorrecto
async function obtenerDatos() {
const response = await fetch('url')
const datos = await response.json()
return datos
}
// Correcto
async function obtenerDatos() {
const response = await fetch('url')
if (!response.ok) {
throw new Error('Error en la petición')
}
const datos = await response.json()
return datos
}Error 3: Enviar body en GET
// Incorrecto: GET no debe tener body
fetch('url', {
method: 'GET',
body: JSON.stringify({ dato: 'valor' })
})
// Correcto: Usa query parameters
fetch('url?dato=valor', {
method: 'GET'
})Error 4: Olvidar Content-Type en POST
// Incorrecto
fetch('url', {
method: 'POST',
body: JSON.stringify({ dato: 'valor' })
// Falta Content-Type header
})
// Correcto
fetch('url', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ dato: 'valor' })
})Error 5: Usar .json() en respuestas no-JSON
// Incorrecto: Si la respuesta es texto plano
async function obtenerTexto() {
const response = await fetch('archivo.txt')
const datos = await response.json() // Error: no es JSON
}
// Correcto
async function obtenerTexto() {
const response = await fetch('archivo.txt')
const texto = await response.text() // Usar .text() para texto plano
}Fetch en diferentes entornos
En Node.js
Fetch esta disponible nativamente desde Node.js 18:
// Node.js 18+
const response = await fetch('https://api.ejemplo.com/datos')
const datos = await response.json()Para versiones anteriores, instala node-fetch:
npm install node-fetch// Node.js < 18
import fetch from 'node-fetch'
const response = await fetch('https://api.ejemplo.com/datos')
const datos = await response.json()En NextJS (Server Components)
// app/page.tsx (Server Component)
export default async function Page() {
const response = await fetch('https://api.ejemplo.com/productos')
const productos = await response.json()
return (
<div>
{productos.map(p => (
<div key={p.id}>{p.nombre}</div>
))}
</div>
)
}En React (Client Components)
'use client'
import { useState, useEffect } from 'react'
export default function Productos() {
const [productos, setProductos] = useState([])
const [cargando, setCargando] = useState(true)
useEffect(() => {
async function cargar() {
const response = await fetch('https://api.ejemplo.com/productos')
const datos = await response.json()
setProductos(datos)
setCargando(false)
}
cargar()
}, [])
if (cargando) return <div>Cargando...</div>
return (
<div>
{productos.map(p => (
<div key={p.id}>{p.nombre}</div>
))}
</div>
)
}Resumen
Puntos clave sobre Fetch:
- Fetch es la forma moderna de hacer peticiones HTTP
- Retorna una Promise que se resuelve con un objeto Response
- Debes llamar
.json(),.text(), o.blob()para obtener los datos - Siempre verifica
response.ok- fetch no rechaza en errores HTTP - Usa
methodpara especificar GET, POST, PUT, DELETE - Usa
headerspara enviar información adicional - Usa
bodypara enviar datos (no en GET) - Siempre maneja errores con try/catch
- Puedes cancelar peticiones con AbortController
- Funciona en navegadores y Node.js 18+
Tabla de referencia rápida:
| Acción | código |
|---|---|
| GET básico | fetch(url) |
| POST con JSON | fetch(url, { method: 'POST', body: JSON.stringify(data) }) |
| Obtener JSON | await response.json() |
| Obtener texto | await response.text() |
| Verificar éxito | if (!response.ok) throw new Error() |
| Con headers | fetch(url, { headers: { 'Authorization': 'Bearer token' } }) |
| Cancelar | fetch(url, { signal: controller.signal }) |
Si necesitas funcionalidades más avanzadas como interceptors, timeouts integrados y conversion automática de JSON, revisa la guía completa de Axios en JavaScript.
Cuando tus respuestas de API incluyen datos que necesitas validar antes de usar en tu aplicación, Zod es una excelente opción para validar esos datos de forma segura con TypeScript.
Recursos adicionales:
Preguntas frecuentes
¿Qué es la Fetch API y cómo se usa en JavaScript?
La Fetch API es una función nativa de JavaScript que permite hacer peticiones HTTP a servidores. Se usa con fetch(url) que retorna una Promise, y puedes encadenar .then() o usar async/await para obtener los datos de la respuesta.
¿Cómo manejar errores con fetch en JavaScript?
Fetch solo rechaza la promesa en errores de red, no en errores HTTP como 404 o 500. Debes verificar response.ok manualmente y lanzar un error si es false. Combina esto con try/catch para manejar tanto errores de red como errores HTTP.
¿Cuál es la diferencia entre fetch y axios?
Fetch es nativo del navegador y no requiere dependencias, pero necesitas convertir manualmente a JSON y verificar errores HTTP. Axios es una librería externa que convierte JSON automáticamente, lanza errores en status 4xx/5xx, y ofrece interceptors y timeouts integrados.
Articulos relacionados
Zod Avanzado: Discriminated Unions, Transforms y Pipes
Patrones avanzados de Zod: discriminated unions, transforms, pipes, preprocess, y como validar datos complejos en TypeScript con schemas reutilizables.
tRPC + Next.js: APIs Type-Safe sin REST
Implementa tRPC en Next.js para APIs 100% type-safe. Sin schemas de API, sin fetch manual, sin types duplicados. End-to-end type safety con TypeScript.
Webhooks en Next.js: Recibe y Procesa Eventos
Implementa webhooks en Next.js para recibir eventos de Stripe, GitHub, Clerk y otros servicios. Verificación de firmas, tipado y manejo de errores.