Apuntes DAM
    Tema 6 de 7DWEC · 2º DAW

    Desarrollo Web en Entorno Cliente

    Asincronía, AJAX y Frameworks

    El bucle de eventos, callbacks, promesas, async y await, fetch con GET y POST, errores, CORS, cancelación con AbortController, estados de carga e introducción a React, Vue y Angular.

    75 min lecturaAvanzado

    1. Por qué JavaScript no espera

    JavaScript ejecuta tu código en un único hilo: hace una cosa detrás de otra. Si una operación lenta, como pedir datos a un servidor que tarda dos segundos en responder, bloqueara ese hilo, la página se quedaría congelada: no respondería a los clics, no podría hacer scroll y el navegador acabaría preguntando si quieres cerrarla. Por eso las operaciones lentas son asíncronas: se ponen en marcha, el código sigue, y cuando terminan se ejecuta lo que hayas dejado preparado.

    El bucle de eventos es un camarero con buena memoria

    Un solo camarero
    Atiende las mesas de una en una: es el único hilo de JavaScript.
    No se queda en la cocina
    Deja la comanda (la petición) y sigue atendiendo. La cocina (el navegador) trabaja por su cuenta.
    La cola de avisos
    Cuando un plato está listo, el aviso espera en una cola y el camarero lo recoge en cuanto queda libre.

    Ese mecanismo se llama bucle de eventos (event loop). Una consecuencia práctica que sorprende al principio: aunque un setTimeout tenga 0 milisegundos de espera, su función no se ejecuta hasta que termina todo el código que se está ejecutando en ese momento.

    javascript
    1console.log("1. Pido los datos");
    2setTimeout(() => console.log("3. Han llegado los datos"), 0);
    3console.log("2. Mientras tanto, sigo trabajando");
    4// Se imprime 1, 2, 3
    ¡Maldito Event Loop! Lo que no sabías de la asincronía en JS — El Cloud de Pau - Programación Web

    2. De los callbacks a las promesas

    La primera forma de manejar la asincronía fueron los callbacks: le pasas a la operación una función para que la llame al terminar. Funciona, pero cuando una operación depende de otra, que depende de otra, el código se anida hacia la derecha hasta volverse ilegible, lo que se conoce como callback hell.

    Las promesas resolvieron el problema. Una promesa es un objeto que representa un resultado que todavía no existe: empieza pendiente y acaba cumplida (con un valor) o rechazada (con un error). Con then dices qué hacer con el valor, con catch qué hacer si falla y con finally qué hacer en cualquier caso. Y como then devuelve otra promesa, las operaciones se encadenan en vertical, no en escalera.

    CombinadorEspera a…Se usa para
    Promise.all([…])Que se cumplan todas; falla si falla unaVarias peticiones independientes que necesitas juntas
    Promise.allSettled([…])Que terminen todas, bien o malMostrar lo que haya salido bien aunque algo falle
    Promise.race([…])La primera que terminePoner un tiempo máximo a una operación
    Promise.any([…])La primera que se cumplaPedir lo mismo a varios servidores y quedarte con el más rápido

    3. async y await: asincronía que se lee de arriba abajo

    async y await son la forma moderna de trabajar con promesas. Una función marcada con async siempre devuelve una promesa, y dentro de ella await pausa la función (solo la función, no la página) hasta que la promesa se resuelve. El resultado es código asíncrono que se lee como si fuera secuencial, y los errores se capturan con el try/catch de toda la vida.

    javascript
    1async function mostrarPedido(id) {
    2  try {
    3    const respuesta = await fetch("/api/pedidos/" + id);
    4    if (!respuesta.ok) throw new Error("El servidor respondió " + respuesta.status);
    5    const pedido = await respuesta.json();
    6    pintarPedido(pedido);
    7  } catch (error) {
    8    mostrarError("No se pudo cargar el pedido");
    9    console.error(error);
    10  }
    11}

    Errores típicos con await

    • Olvidar el await. Sin él obtienes una promesa pendiente en lugar del dato, y verás cosas como [object Promise] en pantalla.
    • Esperar en serie lo que podría ir en paralelo. Dos await seguidos hacen que la segunda petición no empiece hasta que acabe la primera. Si no dependen una de otra, usa Promise.all.
    • await dentro de forEach. forEach no espera a las funciones async. Usa for…of si las quieres en orden, o Promise.all(lista.map(…)) si pueden ir a la vez.
    ¿Cómo funcionan las Promises y Async/Await en JavaScript? [2022] — Carlos Azaustre - AprendiendoDEV

    4. AJAX con fetch

    AJAX es el nombre que se dio en 2005 a la idea que cambió la web: pedir datos al servidor en segundo plano y actualizar solo una parte de la página, sin recargarla. Gmail y Google Maps la popularizaron. La «X» viene de XML, pero hoy los datos viajan casi siempre en JSON, y el viejo XMLHttpRequest ha dado paso a fetch, más sencillo y basado en promesas.

    javascript
    1// GET: leer datos
    2const respuesta = await fetch("/api/productos?categoria=teclados");
    3const productos = await respuesta.json();
    4
    5// POST: enviar datos en JSON
    6await fetch("/api/productos", {
    7  method: "POST",
    8  headers: { "Content-Type": "application/json" },
    9  body: JSON.stringify({ nombre: "Teclado mecánico", precio: 59.9 }),
    10});
    11
    12// Con tiempo máximo: cancela si tarda más de 5 segundos
    13const lenta = await fetch("/api/informe", { signal: AbortSignal.timeout(5000) });

    fetch no falla con un 404

    La promesa de fetch solo se rechaza si no hay respuesta (sin red, servidor caído, petición cancelada). Un 404 o un 500 son respuestas válidas desde su punto de vista. Comprueba siempre respuesta.ok (true para los códigos 200 a 299) antes de usar los datos.

    CORS: por qué a veces el navegador bloquea la petición

    Por seguridad, el navegador solo deja que tu página lea respuestas de su propio origen (mismo protocolo, dominio y puerto). Si tu web en miapp.com pide datos a api.otro.com, el navegador hace la petición pero solo te deja leer la respuesta si el servidor lo autoriza con la cabecera Access-Control-Allow-Origin. Cuando veas un error de CORS en la consola, recuerda que no se arregla en el cliente: es el servidor quien tiene que dar permiso.

    Los cuatro estados de una pantalla con datos

    Una buena interfaz no piensa solo en el caso feliz. Cada vez que cargas datos, tu pantalla puede estar en uno de cuatro estados, y el usuario debe saber siempre en cuál está: cargando (un indicador o un esqueleto), error (un mensaje comprensible y, si se puede, un botón para reintentar), vacío («no hay resultados», mejor que una lista en blanco) y con datos.

    Simula peticiones con promesas
    Consola
    (sin mensajes)
    Cómo CONSUMIR una API REST con JAVASCRIPT y Fetch + Promises con gestión de Errores — midulive

    5. Librerías y frameworks

    Con lo visto hasta aquí puedes construir cualquier cosa. Pero en una aplicación grande, mantener sincronizados los datos y el DOM a mano se vuelve agotador: cada cambio obliga a recordar qué partes de la pantalla hay que actualizar. Los frameworks resuelven justo eso con una idea común: tú describes cómo se ve la interfaz en función del estado, y cuando el estado cambia, el framework actualiza el DOM por ti.

    Contador.jsx (React)
    1import { useState } from "react";
    2
    3export function Contador() {
    4  const [valor, setValor] = useState(0);
    5  return (
    6    <button onClick={() => setValor(valor + 1)}>
    7      Has pulsado {valor} veces
    8    </button>
    9  );
    10}
    ReactVueAngularSvelte
    Qué esLibrería de interfaces (Meta)Framework progresivoFramework completo (Google)Compilador de componentes
    LenguajeJSX + JavaScript o TypeScriptPlantillas HTML + JavaScriptTypeScript obligatorioPlantillas + JavaScript
    Curva de aprendizajeMediaSuaveAltaSuave
    Muy usado enStartups y producto digitalPymes y proyectos que crecen poco a pocoGrandes empresas y bancaProyectos donde prima el rendimiento

    ¿Cuál aprender? En España, React es el que más ofertas de empleo tiene, seguido de Angular. Pero lo más valioso es dominar primero JavaScript sin frameworks: los frameworks cambian cada pocos años y el lenguaje permanece. Quien entiende el DOM, los eventos y las promesas aprende cualquier framework en semanas.

    6. Retos

    En estos retos la página habla con un servidor simulado que responde con un pequeño retraso, igual que uno real. Las pruebas comprueban no solo que los datos lleguen, sino también cómo los pides: en paralelo, con el método y las cabeceras correctas, y cancelando lo que ya no hace falta.

    🧩JS · DOMCargar usuariosMedio

    Al pulsar #cargar: escribe «Cargando…» en #estado, pide GET /api/usuarios y pinta un <li> con el nombre de cada usuario en #lista (vaciándola antes). Al terminar, #estado muestra «3 usuarios» (el número que sea). Si la respuesta no es correcta (response.ok es false) o falla la red, #estado muestra «Error al cargar». Para simular el fallo, el servidor responde 500 cuando window.__fallo vale true.

    index.htmlsolo lectura
    script.js
    Vista previa
    🧩JS · DOMDos peticiones a la vezMedio

    Completa la función async cargarPanel() para que pida a la vez GET /api/perfil (devuelve { nombre }) y GET /api/notas (devuelve un array de números), y devuelva { nombre, media } con la media redondeada a 1 decimal. Las dos peticiones deben lanzarse en paralelo, no una detrás de otra: cada una tarda unos 100 ms.

    index.htmlsolo lectura
    script.js
    Vista previa
    🧩JS · DOMEnviar un formulario con POSTMedio

    Al enviar #contacto, sin recargar la página, envía POST /api/mensajes con un cuerpo JSON { nombre, mensaje } y la cabecera Content-Type: application/json. Mientras se envía, el botón debe estar deshabilitado. Si el servidor responde 201, #resultado muestra «Mensaje enviado» y el formulario se vacía; si no, «No se pudo enviar». El botón vuelve a habilitarse al terminar en ambos casos.

    index.htmlsolo lectura
    script.js
    Vista previa
    🧩JS · DOMBuscador que cancela lo anteriorDifícil

    Con cada cambio en #q, pide GET /api/buscar?q=texto (codificado con encodeURIComponent) y pinta los resultados (un array de textos) como <li> en #resultados. El servidor responde más despacio a las búsquedas cortas, así que una respuesta antigua puede llegar después de una nueva: cancela la petición anterior con AbortController para que la lista muestre siempre los resultados de lo último que se escribió. Una cancelación no es un error y no debe mostrarse nada por ella.

    index.htmlsolo lectura
    script.js
    Vista previa

    ¿Has terminado este tema?

    Crea una cuenta gratis para guardar qué temas has terminado, subir de nivel y ganar medallas.

    Guardar mi progreso