AJAX y Fetch
Cómo habla el navegador con el servidor sin recargar la página: la técnica AJAX, el objeto XMLHttpRequest con sus estados, la API Fetch que lo sustituye, las promesas y el paso de datos entre JavaScript y JSON.
Qué es AJAX y qué cambió
Antes de AJAX, cualquier interacción con el servidor obligaba a recargar la página entera: se pulsaba un botón, el navegador se quedaba en blanco y volvía a pintarlo todo. AJAX es la técnica de desarrollo que rompió ese ciclo. El JavaScript de la página lanza una petición al servidor en segundo plano, el navegador no se queda esperando, y cuando llega la respuesta se actualiza solo la parte de la página que ha cambiado.
La clave está en la palabra asíncrono. La petición se manda y el programa sigue: no hay bloqueo. Cuando el servidor responde, se dispara un evento y el manejador registrado toma el control. Ese cambio es lo que permitió que una aplicación web se pareciera a una aplicación de escritorio, y de ahí el nombre de aplicaciones enriquecidas o RIA.
- Se hace una petición a una URL del servidor desde JavaScript.
- El servidor devuelve datos, normalmente en JSON y antiguamente en XML, no una página entera.
- El JavaScript recibe esos datos y actualiza el DOM: recarga parcial.
- Todo ello sin que el usuario vea la página parpadear.
Ese avance trajo un problema de accesibilidad que conviene conocer, porque se pregunta: si el contenido cambia sin recargar, un lector de pantalla no se entera de que algo se ha actualizado. Para eso existen las guías WAI-ARIA (Web Accessibility Initiative, Accessible Rich Internet Applications), que definen atributos con los que la página avisa de esos cambios y describe el papel de cada zona.
AJAX no es un lenguaje ni una librería: es una técnica. Lo que se apoya en ella es el objeto XMLHttpRequest primero y la API Fetch después.
Para el examen
Qué es: una técnica; ni lenguaje ni librería
Sobre qué se apoya: XMLHttpRequest primero y la API Fetch después
El objeto XMLHttpRequest
XMLHttpRequest, abreviado XHR, es el objeto que todos los navegadores incorporan para lanzar peticiones HTTP asíncronas desde JavaScript. Su nombre despista: aunque lleve XML, sirve igual para recibir JSON, texto o cualquier otro contenido.
| Miembro | Qué es y qué valores importan |
|---|---|
| readyState | Estado del objeto. 0 es sin inicializar y 4 es completado, es decir, ha llegado respuesta, sea buena o mala |
| status | Código de estado HTTP de la respuesta: 200 si fue correcta, 404 si no se encontró |
| responseText | El cuerpo de la respuesta como texto; es donde llega el JSON |
| responseXML | El cuerpo de la respuesta ya analizado como documento XML |
| open(...) | Prepara la llamada: método, URL y, opcionalmente, si es asíncrona y las credenciales |
| send(...) | Envía la petición, con datos si los hay |
| onreadystatechange | Evento que se dispara con cada cambio de readyState |
El detalle que más se pregunta es que readyState y status son dos cosas distintas y hay que mirar las dos. readyState igual a 4 solo dice que la respuesta ya ha llegado; para saber si esa respuesta es buena hay que mirar además que status valga 200. Una respuesta 404 también llega con readyState igual a 4.
const peticion = new XMLHttpRequest();
peticion.onreadystatechange = function () {
// 4 significa que ya hay respuesta, buena o mala
if (peticion.readyState !== 4) return;
// y aquí se comprueba si además fue correcta
if (peticion.status === 200) {
const intento = JSON.parse(peticion.responseText);
pintarResultado(intento);
} else {
mostrarError(peticion.status); // 404, 500...
}
};
peticion.open("GET", "/api/intentos/ultimo", true); // true = asíncrona
peticion.send();El método open admite los verbos habituales de HTTP. GET pide un recurso y sus parámetros viajan en la propia URL, así que quedan en el historial y en los registros del servidor: nunca se manda por GET una contraseña. POST envía datos en el cuerpo de la petición y se usa para crear o para cualquier operación que cambie algo. PUT se usa para reemplazar un recurso existente.
Para el examen
readyState igual a 4: solo dice que la respuesta llegó
Qué hay que mirar además: que status valga 200
Por qué: un 404 también llega con readyState 4
Fetch, la API que sustituye a XMLHttpRequest
El código de XMLHttpRequest es verboso y se apoya en un evento que hay que filtrar a mano. Fetch es la API moderna que lo sustituye: es un estándar vivo de la WHATWG, lo implementan todos los navegadores, y en lugar de eventos devuelve una promesa, lo que permite encadenar los pasos en lugar de anidarlos.
// La misma petición del punto anterior, con Fetch
fetch("/api/intentos/ultimo")
.then(respuesta => {
if (!respuesta.ok) throw new Error(respuesta.status);
return respuesta.json(); // json() también devuelve una promesa
})
.then(intento => pintarResultado(intento))
.catch(error => mostrarError(error));
// Enviar datos con POST
fetch("/api/intentos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tema: "aplicaciones-web", aciertos: 18 }),
});Junto a Fetch se usa mucho Axios, que no es una API del navegador sino una librería externa para hacer peticiones asíncronas. Aporta comodidades que Fetch deja en manos del programador, como convertir la respuesta a JSON automáticamente o tratar como error los códigos de estado de fallo.
Fetch no rechaza la promesa cuando el servidor responde con un 404 o un 500: para Fetch eso es una respuesta que ha llegado bien. Solo falla si no hubo respuesta. Por eso hay que mirar siempre la propiedad ok o el status.
Para el examen
Ante un 404 o un 500: Fetch NO rechaza la promesa
Cuándo falla de verdad: solo si no hubo respuesta
Qué hay que comprobar: las propiedades ok o status
Promesas, async y await
Una promesa es un objeto que representa un resultado que todavía no existe pero que llegará: es la forma que tiene JavaScript de manejar el código asíncrono sin bloquear. Al crearla se indica qué hacer si la cosa sale bien y qué hacer si sale mal.
| Estado de una promesa | Qué significa |
|---|---|
| pending (pendiente) | Todavía no se ha resuelto: la operación sigue en marcha |
| fulfilled (cumplida) | Terminó bien y hay un valor disponible |
| rejected (rechazada) | Terminó mal y hay un motivo de error |
El método then registra qué hacer cuando la promesa se cumpla, y como devuelve otra promesa, se pueden encadenar. El problema es que encadenar muchos then hace el código difícil de seguir, con la lógica desperdigada en funciones sueltas.
Para eso están async y await. La palabra async convierte una función en asíncrona, es decir, hace que devuelva una promesa. Dentro de ella, await espera el resultado de otra función asíncrona y devuelve directamente el valor, sin then. El código queda escrito de arriba abajo, como si fuera síncrono, pero sigue sin bloquear el navegador.
// Con encadenamiento de then
function cargarIntento() {
return fetch("/api/intentos/ultimo")
.then(r => r.json())
.then(intento => pintarResultado(intento))
.catch(e => mostrarError(e));
}
// Lo mismo con async y await: se lee de arriba abajo
async function cargarIntentoAsync() {
try {
const respuesta = await fetch("/api/intentos/ultimo");
const intento = await respuesta.json();
pintarResultado(intento);
} catch (e) {
mostrarError(e);
}
}
// Una función async siempre devuelve una promesa,
// aunque su cuerpo devuelva un número:
// cargarIntentoAsync() -> Promise { <pending> }Para el examen
Promesa: representa un resultado que llegará
then: encadena y devuelve otra promesa
async: hace que la función devuelva una promesa
await: espera el valor sin then, con aspecto de código síncrono
Pasar de JSON a objeto y de objeto a JSON
Todo lo que viaja por HTTP es texto, así que un objeto de JavaScript no se puede enviar tal cual: hay que convertirlo antes en una cadena, y al recibirlo hay que hacer el camino inverso. El objeto global JSON tiene exactamente dos métodos para eso, y confundirlos es un clásico.
| Método | Dirección | Se usa al |
|---|---|---|
| JSON.stringify(objeto) | De objeto de JavaScript a cadena de texto | Enviar datos al servidor o guardarlos en localStorage |
| JSON.parse(cadena) | De cadena de texto a objeto de JavaScript | Recibir la respuesta del servidor o leer de localStorage |
const intento = {
tema: "aplicaciones-web-html-xml-y-scripting",
aciertos: 18,
falladas: ["q3", "q7"],
};
// Objeto -> texto, para enviarlo o guardarlo
const comoTexto = JSON.stringify(intento);
localStorage.setItem("ultimoIntento", comoTexto);
// Texto -> objeto, al recuperarlo
const recuperado = JSON.parse(localStorage.getItem("ultimoIntento"));
console.log(recuperado.falladas.length); // 2stringify convierte a cadena (string) y parse analiza una cadena. La regla mnemotécnica está en el propio nombre: el que lleva string es el que produce el texto.
Para el examen
stringify: convierte un objeto a cadena
parse: analiza una cadena y devuelve un objeto
Regla: el que lleva «string» es el que produce el texto