JS 16: Conectando con el Mundo

Peticiones HTTP, APIs y formato JSON.

1. ¿Qué es JSON?

JSON (JavaScript Object Notation) es un formato de texto ligero para intercambiar datos. Es el estándar de facto para comunicación entre cliente y servidor.

Características

Sintaxis JSON

{

  "nombre": "Ana García",

  "edad": 28,

  "esEstudiante": false,

  "habilidades": ["JavaScript", "Python", "CSS"],

  "direccion": {

    "ciudad": "Madrid",

    "codigoPostal": "28001"

  },

  "email": null

}
?? Reglas estrictas de JSON:
  • Las claves DEBEN estar entre comillas dobles: "nombre"
  • Los strings DEBEN usar comillas dobles: "texto"
  • NO se permiten comentarios
  • NO se permiten trailing commas: { "a": 1, } ?
  • Solo tipos primitivos: string, number, boolean, null, array, object

JSON vs Objetos JavaScript

Aspecto JSON Objeto JS
Tipo Texto (string) Objeto en memoria
Claves "comillas obligatorias" Con o sin comillas
Funciones ? No soportadas ? Soportadas
Undefined ? No existe ? Existe
Comentarios ? No permitidos ? Permitidos

2. JSON.parse() y JSON.stringify()

JSON.stringify() - Objeto ? Texto

Convierte un objeto JavaScript en texto JSON:

const usuario = {

  nombre: 'Carlos',

  edad: 30,

  activo: true,

  hobbies: ['leer', 'programar']

};



// Convertir a JSON string

const jsonString = JSON.stringify(usuario);

console.log(jsonString);

// '{"nombre":"Carlos","edad":30,"activo":true,"hobbies":["leer","programar"]}'



console.log(typeof jsonString); // "string"

Parámetros opcionales de stringify()

const datos = {

  id: 1,

  nombre: 'Producto',

  precio: 99.99,

  privado: 'no mostrar'

};



// Parámetro 2: Replacer (filtrar propiedades)

const jsonFiltrado = JSON.stringify(datos, ['id', 'nombre', 'precio']);

console.log(jsonFiltrado); // {"id":1,"nombre":"Producto","precio":99.99}



// Parámetro 3: Espaciado (para legibilidad)

const jsonBonito = JSON.stringify(datos, null, 2);

console.log(jsonBonito);

/*

{

  "id": 1,

  "nombre": "Producto",

  "precio": 99.99,

  "privado": "no mostrar"

}

*/

JSON.parse() - Texto ? Objeto

Convierte texto JSON en un objeto JavaScript:

const jsonTexto = '{"nombre":"Laura","edad":25,"skills":["JS","CSS"]}';



// Convertir a objeto

const objeto = JSON.parse(jsonTexto);



console.log(objeto.nombre);  // "Laura"

console.log(objeto.edad);    // 25

console.log(objeto.skills);  // ["JS", "CSS"]

console.log(typeof objeto);  // "object"

Manejo de Errores

const jsonInvalido = '{ nombre: "error" }'; // Sin comillas en clave



try {

  const obj = JSON.parse(jsonInvalido);

} catch (error) {

  console.error('JSON inválido:', error.message);

  // JSON inválido: Unexpected token n in JSON at position 2

}

3. Fetch API: Peticiones HTTP

Fetch es la API (Application Programming Interface, que es un conjunto de reglas que permiten que diferentes aplicaciones o partes de un código se comuniquen entre sí) moderna para hacer peticiones HTTP. Reemplaza a XMLHttpRequest y usa Promesas.

Sintaxis Básica

// GET básico

fetch('https://api.example.com/users')

  .then(response => response.json())  // Parsear JSON

  .then(data => console.log(data))    // Usar datos

  .catch(error => console.error(error)); // Manejar errores

Con Async/Await (Forma Moderna)

async function obtenerUsuarios() {

  try {

    const response = await fetch('https://api.example.com/users');

    const usuarios = await response.json();

    console.log(usuarios);

  } catch (error) {

    console.error('Error:', error);

  }

}



obtenerUsuarios();
?? Importante: fetch() NO rechaza la promesa con errores HTTP (404, 500, etc.). Solo rechaza con errores de red. Debes verificar response.ok manualmente.

4. El Objeto Response

Cuando fetch() se resuelve, devuelve un objeto Response:

Propiedades Importantes

Propiedad Descripción
response.ok true si status 200-299
response.status Código HTTP (200, 404, 500...)
response.statusText Texto del status ("OK", "Not Found"...)
response.headers Headers de la respuesta
response.url URL final (después de redirects)

Métodos de Lectura del Body

const response = await fetch('https://api.example.com/data');



// Dependiendo del tipo de respuesta:

const json = await response.json();       // Para JSON

const texto = await response.text();      // Para texto plano

const blob = await response.blob();       // Para archivos binarios

const formData = await response.formData(); // Para formularios

Verificar Éxito de la Petición

async function obtenerDatos() {

  try {

    const response = await fetch('https://api.example.com/users/999');

    

    // ? Verificar si la respuesta es exitosa

    if (!response.ok) {

      throw new Error(`HTTP error! status: ${response.status}`);

    }

    

    const data = await response.json();

    return data;

    

  } catch (error) {

    console.error('Hubo un problema:', error);

  }

}

5. Métodos HTTP con Fetch

GET - Obtener Datos

// GET es el método por defecto

async function obtenerUsuario(id) {

  const response = await fetch(`https://api.example.com/users/${id}`);

  if (!response.ok) throw new Error('Usuario no encontrado');

  return await response.json();

}



obtenerUsuario(1).then(console.log);

POST - Crear Datos

async function crearUsuario(usuario) {

  const response = await fetch('https://api.example.com/users', {

    method: 'POST',

    headers: {

      'Content-Type': 'application/json'

    },

    body: JSON.stringify(usuario)

  });

  

  if (!response.ok) throw new Error('Error al crear usuario');

  return await response.json();

}



const nuevoUsuario = {

  nombre: 'Ana',

  email: 'ana@example.com'

};



crearUsuario(nuevoUsuario).then(console.log);

PUT - Actualizar Completo

async function actualizarUsuario(id, datosCompletos) {

  const response = await fetch(`https://api.example.com/users/${id}`, {

    method: 'PUT',

    headers: {

      'Content-Type': 'application/json'

    },

    body: JSON.stringify(datosCompletos)

  });

  

  return await response.json();

}

PATCH - Actualizar Parcial

async function actualizarParcial(id, cambios) {

  const response = await fetch(`https://api.example.com/users/${id}`, {

    method: 'PATCH',

    headers: {

      'Content-Type': 'application/json'

    },

    body: JSON.stringify(cambios)

  });

  

  return await response.json();

}



// Solo actualizar el email

actualizarParcial(1, { email: 'nuevo@email.com' });

DELETE - Eliminar

async function eliminarUsuario(id) {

  const response = await fetch(`https://api.example.com/users/${id}`, {

    method: 'DELETE'

  });

  

  if (!response.ok) throw new Error('Error al eliminar');

  

  // Algunas APIs devuelven 204 No Content

  if (response.status === 204) {

    return { mensaje: 'Eliminado correctamente' };

  }

  

  return await response.json();

}

🛑 Cancelar Peticiones: AbortController

A veces una petición tarda demasiado o el usuario se va a otra página. Puedes cancelarla para ahorrar recursos.

const controller = new AbortController();
const signal = controller.signal;

// 1. Pasamos la señal al fetch
fetch('https://api.lenta.com/datos', { signal })
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Petición cancelada por el usuario');
    }
  });

// 2. Cuando queramos cancelar:
controller.abort();

6. Trabajando con Headers

Headers Comunes

const options = {

  method: 'POST',

  headers: {

    'Content-Type': 'application/json',      // Tipo de contenido

    'Authorization': 'Bearer TOKEN_AQUI',    // Auth token

    'Accept': 'application/json',            // Tipo esperado

    'X-Custom-Header': 'valor'               // Header custom

  },

  body: JSON.stringify({ dato: 'valor' })

};



fetch('https://api.example.com/data', options);

Leer Headers de la Respuesta

const response = await fetch('https://api.example.com/data');



// Obtener un header específico

const contentType = response.headers.get('Content-Type');

console.log(contentType); // "application/json"



// Verificar si existe un header

if (response.headers.has('X-RateLimit-Remaining')) {

  console.log('Rate limit:', response.headers.get('X-RateLimit-Remaining'));

}



// Iterar todos los headers

for (const [key, value] of response.headers) {

  console.log(`${key}: ${value}`);

}

7. Ejemplo Real: JSONPlaceholder API

JSONPlaceholder es una API fake gratuita para practicar.

CRUD Completo

const API_URL = 'https://jsonplaceholder.typicode.com';



// 1. Obtener todos los posts (GET)

async function obtenerPosts() {

  const response = await fetch(`${API_URL}/posts`);

  const posts = await response.json();

  console.log('Posts:', posts.length);

  return posts;

}



// 2. Obtener un post específico (GET)

async function obtenerPost(id) {

  const response = await fetch(`${API_URL}/posts/${id}`);

  if (!response.ok) {

    throw new Error('Post no encontrado');

  }

  return await response.json();

}



// 3. Crear un post (POST)

async function crearPost(datos) {

  const response = await fetch(`${API_URL}/posts`, {

    method: 'POST',

    headers: {

      'Content-Type': 'application/json'

    },

    body: JSON.stringify(datos)

  });

  

  const nuevoPost = await response.json();

  console.log('Post creado con ID:', nuevoPost.id);

  return nuevoPost;

}



// 4. Actualizar post (PUT)

async function actualizarPost(id, datos) {

  const response = await fetch(`${API_URL}/posts/${id}`, {

    method: 'PUT',

    headers: {

      'Content-Type': 'application/json'

    },

    body: JSON.stringify(datos)

  });

  

  return await response.json();

}



// 5. Eliminar post (DELETE)

async function eliminarPost(id) {

  const response = await fetch(`${API_URL}/posts/${id}`, {

    method: 'DELETE'

  });

  

  if (response.ok) {

    console.log(`Post ${id} eliminado`);

  }

}



// Usar las funciones

async function demo() {

  try {

    // Obtener posts

    const posts = await obtenerPosts();

    console.log('Primer post:', posts[0]);

    

    // Obtener un post específico

    const post = await obtenerPost(1);

    console.log('Post 1:', post);

    

    // Crear nuevo post

    const nuevo = await crearPost({

      title: 'Mi Post',

      body: 'Contenido del post',

      userId: 1

    });

    console.log('Creado:', nuevo);

    

    // Actualizar

    const actualizado = await actualizarPost(1, {

      id: 1,

      title: 'Título Actualizado',

      body: 'Contenido actualizado',

      userId: 1

    });

    console.log('Actualizado:', actualizado);

    

    // Eliminar

    await eliminarPost(1);

    

  } catch (error) {

    console.error('Error:', error);

  }

}



demo();

8. Query Parameters (Parámetros de URL)

Para filtrar, paginar o buscar datos:

Forma Manual

const userId = 1;

const limite = 10;



const url = `https://api.example.com/posts?userId=${userId}&_limit=${limite}`;



const response = await fetch(url);

const posts = await response.json();

Con URLSearchParams (Recomendado)

const params = new URLSearchParams({

  userId: 1,

  _limit: 10,

  _sort: 'id',

  _order: 'desc'

});



const url = `https://jsonplaceholder.typicode.com/posts?${params}`;

console.log(url);

// https://jsonplaceholder.typicode.com/posts?userId=1&_limit=10&_sort=id&_order=desc



const response = await fetch(url);

const posts = await response.json();

9. Manejo de Errores Robusto

Patrón Completo

async function peticionRobusta(url, options = {}) {

  try {

    // 1. Hacer la petición

    const response = await fetch(url, options);

    

    // 2. Verificar errores HTTP

    if (!response.ok) {

      const errorData = await response.json().catch(() => ({}));

      throw new Error(

        `HTTP ${response.status}: ${errorData.message || response.statusText}`

      );

    }

    

    // 3. Parsear respuesta

    const data = await response.json();

    return { success: true, data };

    

  } catch (error) {

    // Si no hay conexión o error de red

    if (error.name === 'TypeError') {

      return {

        success: false,

        error: 'Error de conexión. Verifica tu internet.'

      };

    }

    

    // Otros errores

    return {

      success: false,

      error: error.message

    };

  }

}



// Uso

const resultado = await peticionRobusta('https://api.example.com/users');



if (resultado.success) {

  console.log('Datos:', resultado.data);

} else {

  console.error('Error:', resultado.error);

}

10. Añadir Timeout a Fetch

Fetch no tiene timeout incorporado. Usa AbortController:

async function fetchConTimeout(url, timeout = 5000) {

  const controller = new AbortController();

  const timeoutId = setTimeout(() => controller.abort(), timeout);

  

  try {

    const response = await fetch(url, {

      signal: controller.signal

    });

    

    clearTimeout(timeoutId);

    return await response.json();

    

  } catch (error) {

    if (error.name === 'AbortError') {

      throw new Error('La petición tardó demasiado (timeout)');

    }

    throw error;

  }

}



// Uso

try {

  const data = await fetchConTimeout('https://api.example.com/slow', 3000);

  console.log(data);

} catch (error) {

  console.error(error.message);

}

11. Ejercicios Prácticos

Ejercicio 1: JSON Básico

Convierte este objeto a JSON y luego de vuelta a objeto:

const producto = {

  id: 101,

  nombre: 'Laptop',

  precio: 899.99,

  disponible: true

};



// 1. Convertir a JSON string

// 2. Parsear de vuelta a objeto

// 3. Mostrar el nombre

Ejercicio 2: Fetch Básico

Obtén 5 posts de JSONPlaceholder:

// URL: https://jsonplaceholder.typicode.com/posts?_limit=5

// 1. Hacer fetch

// 2. Convertir a JSON

// 3. Mostrar los títulos en consola

Ejercicio 3: POST con Fetch

Crea un nuevo usuario en JSONPlaceholder:

// URL: https://jsonplaceholder.typicode.com/users

// Datos: { name: 'Tu nombre', email: 'tu@email.com', username: 'tunombre' }

// Muestra el ID del usuario creado

Ejercicio 4: Manejo de Errores

Intenta obtener un post que no existe (ID 9999) y maneja el error:

// URL: https://jsonplaceholder.typicode.com/posts/9999

// Usa try/catch y verifica response.ok

Resumen

Siguiente paso: En la próxima lección aprenderás sobre LocalStorage y SessionStorage para guardar datos en el navegador.

Volver a JS Volver a JS