- Fundamentos de XMLHttpRequest
El objeto XMLHttpRequest (conocido como XHR) es una API nativa del navegador que permite realizar solicitudes HTTP de manera asíncrona. Librerías populares como jQuery o Axios basan su funcionamiento interno en este objeto o en la más moderna API Fetch.
1.1 Realización de una solicitud GET
El ciclo de vida de una petición básica consta de cuatro pasos: instanciación, configuración, envío y escucha de la respuesta.
// 1. Instanciar el objeto
const solicitud = new XMLHttpRequest();
// 2. Configurar el método y la URL
solicitud.open('GET', 'https://api.ejemplo.com/v1/libros');
// 3. Enviar la petición
solicitud.send();
// 4. Escuchar los cambios de estado
solicitud.addEventListener('readystatechange', () => {
if (solicitud.readyState === 4 && solicitud.status === 200) {
console.log('Respuesta del servidor:', solicitud.responseText);
}
});
1.2 Parámetros en la URL y Cadenas de Consulta
Para enviar datos en una petición GET, se deben adjuntar a la URL mediante una cadena de consulta (Query String). Esta comienza con el símbolo ? y separa múltiples pares clave-valor con &.
// Sin parámetros
solicitud.open('GET', 'https://api.ejemplo.com/v1/libros');
// Con un parámetro
solicitud.open('GET', 'https://api.ejemplo.com/v1/libros?id=42');
// Con múltiples parámetros
solicitud.open('GET', 'https://api.ejemplo.com/v1/libros?id=42&titulo=El+Quijote');
Independientemente de si utilizas XHR puro, $.ajax o fetch, el mecanismo subyacente para las peticiones GET siempre implica concatenar estos parámetros a la URL.
1.3 Estados de la Petición (readyState)
La propiedad readyState indica la fase actual del ciclo de vida de la solicitud:
| Valor | Estado | Descripción |
|---|---|---|
| 0 | UNSENT | Objeto creado, pero open() no ha sido llamado. |
| 1 | OPENED | open() ha sido invocado. |
| 2 | HEADERS_RECEIVED | send() ejecutado y cabeceras de respuesta recibidas. |
| 3 | LOADING | Descargando datos; responseText contiene información parcial. |
| 4 | DONE | Operación completada (con éxito o error). |
1.4 Codificación de URLs
Las URLs solo admiten un subconjunto específico de caracteres ASCII. Para incluir caracteres especiales o acentos, es obligatorio codificarlos. Los navegadores modernos suelen hacerlo automáticamente, pero es útil conocer las funciones nativas encodeURI() y decodeURI().
const textoOriginal = "Cien Años de Soledad";
const textoCodificado = encodeURI(textoOriginal);
console.log(textoCodificado); // Cien%20A%C3%B1os%20de%20Soledad
const textoDecodificado = decodeURI(textoCodificado);
console.log(textoDecodificado); // Cien Años de Soledad
1.5 Realización de una solicitud POST
Las peticiones POST envían los datos en el cuerpo de la solicitud. Es crucial establecer la cabecera Content-Type adecuada para que el servidor sepa cómo interpretar la información.
const peticionPost = new XMLHttpRequest();
peticionPost.open('POST', 'https://api.ejemplo.com/v1/libros');
// Establecer el tipo de contenido para datos de formulario
peticionPost.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// Enviar los datos en el cuerpo
peticionPost.send('titulo=1984&autor=George+Orwell&editorial=Planeta');
peticionPost.addEventListener('load', () => {
if (peticionPost.status >= 200 && peticionPost.status < 300) {
console.log('Libro creado:', peticionPost.responseText);
}
});
- Formatos de Intercambio de Datos
La comunicación entre cliente y servidor requiere un formato estructurado. Históriacmente se utilizó XML, pero hoy en día JSON es el estándar indiscutible debido a su ligereza y facilidad de parseo en JavaScript.
2.1 XML vs JSON
- XML: Lenguaje de marcado extensible. Es verboso, pesado y complejo de navegar en el navegador.
- JSON (JavaScript Object Notation): Formato de texto ligero basado en la sintaxis de objetos y arrays de JavaScript.
2.2 Reglas de Sintaxis de JSON
- Las claves (propiedades) deben estar entre comillas dobles
". - Los valores de tipo string también requieren comillas dobles.
- No se permiten comentarios ni funciones.
- El valor
undefinedno es válido. - La estructura raíz debe ser un objeto
{}o un array[].
2.3 Serialización y Parseo
Convertir un objeto de JavaScript a una cadena JSON se denomina serialización, mientras que el proceso inverso es parseo o deserialización.
// Serialización (Objeto a String)
const objetoLibro = { id: 1, titulo: "Dune", disponible: true };
const jsonString = JSON.stringify(objetoLibro);
// Parseo (String a Objeto)
const respuestaServidor = '{"id": 2, "titulo": "Fundación", "disponible": false}';
const objetoParseado = JSON.parse(respuestaServidor);
- Creación de una Función Ajax Personalizada
Para simplificar la verbosidad de XHR, podemos encapsular la lógica en una función reutilizable que acepte un objeto de configuración.
3.1 Procesamiento de Parámetros
Primero, necesitamos una función auxiliar para transformar un objeto de datos en una cadena de consulta.
function construirQueryString(objetoDatos) {
if (!objetoDatos) return '';
const pares = [];
for (const clave in objetoDatos) {
if (Object.hasOwn(objetoDatos, clave)) {
pares.push(`${encodeURIComponent(clave)}=${encodeURIComponent(objetoDatos[clave])}`);
}
}
return pares.join('&');
}
3.2 Implementación de la Función Principal
function peticionAjax(configuracion) {
const { metodo = 'GET', url, datos, exito, error } = configuracion;
const xhr = new XMLHttpRequest();
let urlFinal = url;
const metodoUpper = metodo.toUpperCase();
if (metodoUpper === 'GET' && datos) {
const queryString = construirQueryString(datos);
urlFinal += (urlFinal.includes('?') ? '&' : '?') + queryString;
}
xhr.open(metodoUpper, urlFinal);
if (metodoUpper === 'POST' && datos) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(construirQueryString(datos));
} else {
xhr.send();
}
xhr.addEventListener('readystatechange', () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
exito && exito(JSON.parse(xhr.responseText));
} else {
error && error(xhr.statusText);
}
}
});
}
// Uso de la función
peticionAjax({
metodo: 'POST',
url: 'https://api.ejemplo.com/v1/libros',
datos: { titulo: 'El Hobbit', autor: 'J.R.R. Tolkien' },
exito: (respuesta) => console.log('Éxito:', respuesta),
error: (err) => console.error('Error:', err)
});
- Novedades de XMLHttpRequest Level 2
La versión original de XHR tenía limitaciones severas, como la incapacidad para manejar archivos binarios o medir el progreso de la transferencia. Level 2 introdujo mejoras cruciales.
4.1 Tiempo de Espera (Timeout)
Permite abortar la petición si el servidor no responde en un tiempo definido, mejorando la experiencia de usuario en redes inestables.
const xhrTiempo = new XMLHttpRequest();
xhrTiempo.timeout = 5000; // 5 segundos
xhrTiempo.addEventListener('timeout', () => {
console.warn('La solicitud ha excedido el tiempo de espera.');
});
xhrTiempo.open('GET', 'https://api.ejemplo.com/v1/libros/lentos');
xhrTiempo.send();
4.2 Gestión de Formularios con FormData
El objeto FormData permite recopilar datos de formularios HTML o construir pares clave-valor dinámicamente, fcailitando el envío de datos multipart/form-data.
const formulario = document.querySelector('#miFormulario');
formulario.addEventListener('submit', (evento) => {
evento.preventDefault();
// Captura automática de todos los campos del formulario
const datosForm = new FormData(formulario);
const xhrForm = new XMLHttpRequest();
xhrForm.open('POST', 'https://api.ejemplo.com/v1/usuarios');
// No es necesario establecer Content-Type, el navegador lo hace al usar FormData
xhrForm.send(datosForm);
});
4.3 Carga de Archivos y Barra de Progreso
XHR Level 2 permite subir archivos y monitorear el progreso mediante el evento progress en la propiedad upload.
Estructura HTML:
<input type="file" id="selectorArchivo" />
<button id="btnSubir">Subir Documento</button>
<div class="barra-progreso" id="barra" style="width: 0%; height: 20px; background: blue;"></div>
Lógica JavaScript:
const btnSubir = document.getElementById('btnSubir');
const barraProgreso = document.getElementById('barra');
btnSubir.addEventListener('click', () => {
const archivo = document.getElementById('selectorArchivo').files[0];
if (!archivo) {
alert('Por favor, selecciona un archivo.');
return;
}
const datosSubida = new FormData();
datosSubida.append('documento', archivo);
const xhrSubida = new XMLHttpRequest();
// Escuchar el progreso de la subida
xhrSubida.upload.addEventListener('progress', (evento) => {
if (evento.lengthComputable) {
const porcentaje = Math.round((evento.loaded / evento.total) * 100);
barraProgreso.style.width = `${porcentaje}%`;
barraProgreso.textContent = `${porcentaje}%`;
}
});
xhrSubida.addEventListener('load', () => {
if (xhrSubida.status === 200) {
console.log('Archivo subido correctamente.');
}
});
xhrSubida.open('POST', 'https://api.ejemplo.com/v1/upload');
xhrSubida.send(datosSubida);
});
- Simplificación con jQuery y Axios
Aunque XHR es la base, en el desarrollo moderno solemos utilizar abstracciones que simplifican la sintaxis y manejan automáticamente promesas y serialización.
5.1 Subida de Archivos con jQuery
Al usar $.ajax con FormData, es imperativo desactivar el procesamiento automático de datos y la configuración del tipo de contenido.
$('#btnSubirJQ').on('click', function() {
const archivo = $('#selectorArchivo')[0].files[0];
if (!archivo) return alert('Selecciona un archivo');
const datos = new FormData();
datos.append('documento', archivo);
$.ajax({
url: 'https://api.ejemplo.com/v1/upload',
type: 'POST',
data: datos,
processData: false, // Evitar que jQuery transforme el FormData en string
contentType: false, // Dejar que el navegador establezca el boundary correcto
success: (res) => console.log('Éxito con jQuery:', res),
error: (err) => console.error('Error con jQuery:', err)
});
});
5.2 Introducción a Axios
Axios es un cliente HTTP basado en promesas que funciona tanto en el navegador como en Node.js. Su API es mucho más limpia y maneja la conversión JSON automáticamente.
// Petición GET con Axios
axios.get('https://api.ejemplo.com/v1/libros', {
params: { id: 42 }
})
.then(respuesta => console.log(respuesta.data))
.catch(error => console.error(error));
// Petición POST con Axios
axios.post('https://api.ejemplo.com/v1/libros', {
titulo: 'Crimen y Castigo',
autor: 'Fiódor Dostoyevski'
})
.then(respuesta => console.log('Creado:', respuesta.data));