Volver
Ctrl+Space
◑
Copiar
Descargar
Restaurar
Ejecutar
Ctrl+Enter
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <title>Fetch API — Ejemplo 4</title> <style> body { font-family: Arial, sans-serif; padding: 20px; background: #fff; max-width: 640px; } h3 { margin: 22px 0 8px; font-size: 15px; color: #333; } button { padding: 8px 18px; background: #F7DF1E; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; font-size: 14px; margin: 4px 4px 4px 0; transition: background 0.15s; } button:hover { background: #e6cc00; } button:disabled { background: #eee; color: #aaa; cursor: not-allowed; } .btn-danger { background: #fee2e2; color: #dc2626; } .btn-danger:hover { background: #fca5a5; } .loading { display: flex; align-items: center; gap: 10px; background: #fffde7; border: 1px solid #F7DF1E; color: #856404; padding: 14px 16px; border-radius: 8px; font-size: 13px; margin-top: 10px; } .spinner { width: 16px; height: 16px; border-radius: 50%; border: 3px solid #F7DF1E; border-top-color: transparent; animation: girar 0.7s linear infinite; } @keyframes girar { to { transform: rotate(360deg); } } .error { background: #fef2f2; border: 1px solid #fca5a5; color: #991b1b; padding: 14px 16px; border-radius: 8px; font-size: 13px; margin-top: 10px; } .error button { margin-top: 8px; } .item { border: 1px solid #eee; border-radius: 8px; padding: 12px 16px; margin-top: 8px; background: #fafafa; } .item h4 { margin: 0 0 4px; font-size: 14px; color: #333; } .item p { margin: 0; font-size: 12px; color: #777; } .vacio { text-align: center; color: #bbb; padding: 30px; font-size: 14px; } .controles { display: flex; gap: 8px; align-items: center; margin-bottom: 6px; flex-wrap: wrap; } label { font-size: 13px; color: #555; } select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; } </style> </head> <body> <h2>Fetch API — App completa con estados</h2> <p style="font-size:13px;color:#555;"> El patrón que se repite en cualquier app real: mostrar un <strong>loading</strong> mientras esperamos, el <strong>resultado</strong> si todo salió bien, o un <strong>error con botón de reintentar</strong> si algo falló. </p> <div class="controles"> <label for="cantidad">Cantidad de posts:</label> <select id="cantidad"> <option value="5">5</option> <option value="10">10</option> <option value="20">20</option> </select> <button id="btnCargar">Cargar posts</button> <button id="btnForzarError" class="btn-danger">Forzar error (a propósito)</button> </div> <div id="contenido"> <div class="vacio">Elegí una opción y hacé clic en "Cargar posts"</div> </div> <script> // Fetch API en JavaScript - Ejemplo 4: App completa con estados de carga const BASE = "https://jsonplaceholder.typicode.com"; const contenedor = document.getElementById("contenido"); let ultimaAccion = null; // guardamos qué función reintentar // ── Estado: loading ─────────────────────────────────────────────────── function mostrarLoading() { contenedor.innerHTML = ` <div class="loading"> <span class="spinner"></span> Cargando posts... </div>`; } // ── Estado: error (con botón de reintentar) ─────────────────────────── function mostrarError(mensaje) { contenedor.innerHTML = ` <div class="error"> ❌ ${mensaje} <br> <button id="btnReintentar">🔄 Reintentar</button> </div>`; document.getElementById("btnReintentar").addEventListener("click", () => { if (ultimaAccion) ultimaAccion(); }); } // ── Estado: datos cargados ──────────────────────────────────────────── function mostrarDatos(posts) { if (posts.length === 0) { contenedor.innerHTML = `<div class="vacio">No hay posts para mostrar.</div>`; return; } contenedor.innerHTML = posts .map(p => ` <div class="item"> <h4>#${p.id} — ${p.title}</h4> <p>${p.body.slice(0, 90)}...</p> </div>`) .join(""); } // ── Cargar posts normalmente ────────────────────────────────────────── async function cargarPosts() { ultimaAccion = cargarPosts; const cantidad = document.getElementById("cantidad").value; mostrarLoading(); try { const res = await fetch(`${BASE}/posts?_limit=${cantidad}`); if (!res.ok) throw new Error(`Error ${res.status}: ${res.statusText}`); const datos = await res.json(); mostrarDatos(datos); } catch (error) { mostrarError(error.message); } } // ── Forzar un error a propósito (URL que no existe) ─────────────────── async function cargarConError() { ultimaAccion = cargarConError; mostrarLoading(); try { const res = await fetch(`${BASE}/esto-no-existe-en-la-api`); if (!res.ok) throw new Error(`Error ${res.status}: recurso no encontrado`); const datos = await res.json(); mostrarDatos(datos); } catch (error) { mostrarError(error.message); } } document.getElementById("btnCargar").addEventListener("click", cargarPosts); document.getElementById("btnForzarError").addEventListener("click", cargarConError); </script> </body> </html>