learning-front

Nivel 3 · JavaScript moderno y asíncrono

JSON y fetch: hablar con el servidor

Pedir datos a una API con fetch y pasar del JSON (texto) a objetos con los que trabajar, apoyándote en async/await y comprobando los errores HTTP.

En el capítulo anterior, los datos “llegaban” de una API falsa. Ahora aprendes a pedirlos de verdad a un servidor con fetch. Pero antes de eso, una pieza que va siempre con la red: los datos no viajan como objetos de JavaScript, sino como texto en un formato llamado JSON.

JSON: datos como texto#

JSON (JavaScript Object Notation) es un formato de texto para intercambiar datos. Se parece muchísimo a cómo escribes un objeto en JavaScript, pero es un string con reglas más estrictas:

  • Las claves van siempre entre comillas dobles: "nombre", no nombre.
  • Solo admite datos: strings, números, booleanos, null, arrays y objetos. Nada de funciones, ni comentarios, ni comas finales.
json
{
  "nombre": "Tracer",
  "rol": "Daño",
  "partidas": 120,
  "victorias": 78
}

Como JSON es texto y tú trabajas con objetos, necesitas traducir en ambos sentidos. Para eso hay dos funciones del objeto global JSON:

  • JSON.stringify(valor) — de objeto a string JSON. Para enviar datos.
  • JSON.parse(texto) — de string JSON a objeto. Para usar datos recibidos.
javascript
const heroe = { nombre: 'Tracer', rol: 'Daño' };

// Objeto → texto JSON (lo que enviarías al servidor).
const texto = JSON.stringify(heroe);
// texto → '{"nombre":"Tracer","rol":"Daño"}'  (un string)

// Texto JSON → objeto (lo que haces con lo que recibes).
const otra = JSON.parse('{"nombre":"Mercy","rol":"Apoyo"}');
// otra → { nombre: 'Mercy', rol: 'Apoyo' }  (un objeto de verdad)
// Mercy
console.log(otra.nombre);

Pruébalo: JSON.stringify y JSON.parse#

fetch: pedir datos al servidor#

fetch(url) es la función del navegador para hacer una petición HTTP. Devuelve una promesa (de ahí todo lo del capítulo anterior). Pero ojo a un detalle que confunde al principio: la promesa de fetch no se resuelve con los datos, sino con un objeto Response —la respuesta HTTP: su estado, sus cabeceras—. Para obtener el cuerpo ya parseado, llamas a response.json(), que es otra promesa.

Por eso un fetch típico tiene dos await:

javascript
async function cargarHeroes() {
  // 1) Espera la respuesta del servidor. response es un objeto Response.
  const response = await fetch('https://api.tuequipo.dev/heroes');

  // 2) Lee el cuerpo y conviértelo de JSON a objetos. Otra espera.
  const heroes = await response.json();

  // 3) heroes ya es un array normal: úsalo como siempre.
  console.log(heroes.length);
}

(El playground de abajo lo hace de verdad: una petición real con fetch a una API pública abierta. La mecánica —fetch → response.ok → response.json()— es la misma contra cualquier servidor.)

El error que todo el mundo olvida: response.ok#

Aquí va la trampa más famosa de fetch: solo rechaza la promesa ante un fallo de red (no hay conexión, el dominio no existe). Si el servidor responde con un 404 (no encontrado) o un 500 (error del servidor), eso es una respuesta HTTP válida, así que fetch se resuelve con normalidad. Si no lo compruebas, intentarías leer como datos una página de error.

La respuesta trae response.ok (true si el estado es 2xx) y response.status (el código numérico). Compruébalo siempre:

javascript
async function cargarHeroes() {
  const response = await fetch('https://api.tuequipo.dev/heroes');

  // fetch NO rechaza ante 404/500: lo detectamos a mano.
  if (!response.ok) {
    throw new Error('El servidor respondió ' + response.status);
  }

  return response.json();
}

Combinado con try/catch, cubres los dos tipos de fallo: el de red (lo lanza fetch) y el de HTTP (lo lanzas tú al ver !response.ok).

javascript
async function mostrar() {
  try {
    // puede fallar por red o por HTTP
    const heroes = await cargarHeroes();
    console.log('Llegaron ' + heroes.length + ' héroes');
  } catch (error) {
    console.log('No se pudo cargar: ' + error.message);
  }
}

Enviar datos: una nota sobre POST#

fetch por defecto hace una petición GET (pedir). Para enviar datos (crear un héroe, por ejemplo) le pasas un segundo argumento con el método, las cabeceras y el cuerpo, que va como texto JSON (de ahí JSON.stringify).

Una cabecera (header) es metadato que viaja junto a la petición: le dice al servidor cosas sobre lo que le mandas, sin ser el dato en sí. Content-Type: application/json significa exactamente “el cuerpo de esta petición es texto JSON”: así el servidor sabe cómo interpretarlo.

javascript
// Conceptual: enviar un héroe nuevo al servidor.
await fetch('https://api.tuequipo.dev/heroes', {
  // el verbo: crear
  method: 'POST',
  // metadato: le indica al servidor que el cuerpo es JSON
  headers: { 'Content-Type': 'application/json' },
  // objeto → texto JSON
  body: JSON.stringify({ nombre: 'Echo', rol: 'Daño' }),
});

Lo verás a fondo cuando montes un backend; por ahora, quédate con que recibir es response.json() y enviar es body: JSON.stringify(...).

Pruébalo: fetch de verdad#

Esto hace una petición real con fetch a una API pública (una lista de usuarios de ejemplo) y muestra los primeros en la consola. Rompe la URL —quítale una letra al dominio— y verás cómo fetch rechaza por fallo de red y el catch lo recoge. Pulsa Ejecutar (o Ctrl+Enter) para lanzar la petición.

Comprueba lo que sabes#

Pregunta 1 de 5

¿Qué es JSON?

Tu turno#

Pide los héroes a la API con fetch, conviértelos de JSON a objetos y muéstralos con su winrate por la consola. Comprueba response.ok y controla los errores. Cuando lo tengas (o si te atascas), despliega las soluciones y fíjate en cómo el nivel Excelente aísla la llamada y da forma a los datos.

Ejercicio · en esta página

Pide los héroes con fetch

Pide los héroes a la API (URL_HEROES) con fetch, conviértelos de JSON a objetos con response.json() y muéstralos con su winrate por la consola. Controla los errores, comprobando también response.ok.

Paso 1: Que funcione

  • Pides los datos con fetch y los conviertes con response.json().
  • Muestras los héroes por la consola al llegar.
  • Atrapas un posible error de red.
Ver soluciones
// ════════════════════════════════════════════════════════════════════════════
// NIVEL OK — "que funcione"
//
// La mecánica de fetch con .then: fetch(url) da un Response; response.json() lo
// convierte de JSON a objetos (otra promesa); cuando llega, muestras en consola.
// Un .catch mínimo atrapa el fallo de red.
//
// Su límite (lo pule Mejor): no comprueba response.ok, así que un error HTTP
// (404, 500) pasaría como si nada e intentaría leer un cuerpo que no toca; y
// encadenar .then es más farragoso que async/await.
// ════════════════════════════════════════════════════════════════════════════

// ── La "URL del servidor" ──────────────────────────────────────────────────
const heroes = [
  { nombre: "Tracer", rol: "Daño", partidas: 120, victorias: 78 },
  { nombre: "Mercy", rol: "Apoyo", partidas: 200, victorias: 130 },
  { nombre: "Reinhardt", rol: "Tanque", partidas: 90, victorias: 51 },
  { nombre: "Ana", rol: "Apoyo", partidas: 110, victorias: 66 },
];
const URL_HEROES =
  "data:application/json," + encodeURIComponent(JSON.stringify(heroes));

// ── Solución ────────────────────────────────────────────────────────────────
function mostrar(lista) {
  console.log("Héroes cargados: " + lista.length);
  lista.forEach(function (h) {
    var winrate = ((h.victorias / h.partidas) * 100).toFixed(1);
    console.log(h.nombre + " (" + h.rol + ") — " + winrate + "%");
  });
}

// fetch da un Response; .json() lo convierte de JSON a objetos.
// .then encadena el siguiente paso cuando la promesa se resuelve.
fetch(URL_HEROES)
  // devuelve OTRA promesa: el cuerpo parseado
  .then(function (response) {
    return response.json();
  })
  .then(function (lista) {
    mostrar(lista);
  })
  .catch(function (error) {
    console.log("No se pudieron cargar los héroes: " + error.message);
  });

Por qué este nivel

  • La mecánica con .then: fetch da un Response, response.json() lo convierte de JSON a objetos, y muestras en consola al llegar.
  • Un .catch atrapa el fallo de red. Funciona.
  • Su límite: no comprueba response.ok, así que un 404/500 pasaría como si fueran datos buenos.