{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Comunicación con el servidor\n", "\n", "## AJAX en JavaScript\n", "\n", "**Lectura recomendada:** https://developer.mozilla.org/es/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data\n", "\n", "AJAX, acrónimo de \"Asynchronous JavaScript and XML\", es un conjunto de tecnologías que permite a las aplicaciones web enviar y recibir datos del servidor de manera asíncrona, sin necesidad de recargar la página completa. Esto mejora significativamente el rendimiento y la experiencia del usuario. No obstante tiene unos inconvenientes que también hay que mencionar. De esta manera, originalmente, AJAX se compone de: \n", "\n", "1. **JavaScript**: El lenguaje de programación que controla la interacción y el comportamiento dinámico de la página web.\n", "2. **XHTML y CSS**: Utilizados para estructurar y estilizar la página web.\n", "3. **XML o JSON**: Formatos de datos que se envían y reciben desde el servidor.\n", "4. **XMLHttpRequest**: El objeto que permite a JavaScript realizar solicitudes HTTP de manera asíncrona.\n", "\n", "> Esta es la definición original de AJAX, no obstante, aunque se puede mantener el nombre a la metodología, algunas tecnologías han mejorado. Ahora, en general, se usa `JSON` y en vez de `XMLHttpRequest` se usa `fetch`. \n", "\n", "Con AJAX, JavaScript puede enviar o solicitar datos en formato XML o JSON al servidor sin recargar la página. El servidor responde a estas solicitudes, generalmente a través de una API REST o similar. El cliente, usando JavaScript, procesa la respuesta y actualiza el contenido de la página dinámicamente.\n", "\n", "### Ejemplo Básico de AJAX\n", "\n", "#### Enviar una solicitud AJAX con `XMLHttpRequest`\n", "\n", "```html\n", "\n", "\n", "\n", " AJAX Example\n", "\n", "\n", " \n", "
\n", "\n", " \n", "\n", "\n", "```\n", "\n", "En este ejemplo, cuando se hace clic en el botón \"Load Data\", se envía una solicitud `GET` al servidor. La respuesta, que se espera esté en formato JSON, se procesa y se muestra en el `div` con el ID \"result\".\n", "\n", "> Este ejemplo se puede considerar anticuado, a partir de ES6 es mejor hacerlo con fetch y promesas. No obstante, es interesante analizar este código y entender cómo funciona. Además, fetch no permite un control a tan bajo nivel de todas las etapas de una petición, por lo que sigue siendo necesario para hacer barras de progreso, cancelar peticiones... \n", "\n", "### Beneficios de AJAX\n", "\n", "- **Mejora del Rendimiento**: Al no recargar la página completa, solo se actualizan las partes necesarias, lo que resulta en una experiencia de usuario más rápida y fluida.\n", "- **Experiencia de Usuario Mejorada**: Las actualizaciones dinámicas permiten una interacción más rápida y eficiente.\n", "\n", "### Problemas de AJAX\n", "\n", "- **SEO**: Las páginas que utilizan AJAX pueden ser más difíciles de indexar por los motores de búsqueda, ya que gran parte del contenido se carga de manera dinámica.\n", "- **Complejidad en el Desarrollo**: El desarrollo de aplicaciones AJAX puede ser más complicado debido a la necesidad de manejar las solicitudes asíncronas y actualizar el DOM dinámicamente.\n", "\n", "### APIs\n", "\n", "La comunicación entre el cliente y el servidor en una aplicación web puede llevarse a cabo de varias maneras. Dos enfoques comunes incluyen:\n", "\n", "1. **Solicitudes de HTML:** JavaScript puede solicitar un HTML estático o dinámico y luego insertar el resultado en la página.\n", "2. **Interacción con APIs:** JavaScript puede enviar y recibir datos en formato XML o JSON a través de una API.\n", "\n", "Las APIs (Interfaz de Programación de Aplicaciones) permiten que diferentes software se comuniquen entre sí. Existen varios tipos de APIs, cada una con sus propias características y casos de uso:\n", "\n", "#### Tipos de APIs\n", "\n", "1. **SOAP (Simple Object Access Protocol):** \n", " - Es un protocolo basado en XML.\n", " - Conocido por su complejidad y sobrecarga debido a la utilización de XML.\n", " - No está optimizado para HTTP.\n", "\n", "2. **REST (Representational State Transfer):**\n", " - Basado en HTTP y utiliza URLs.\n", " - Aprovecha los verbos HTTP (GET, POST, PUT, DELETE, PATCH).\n", " - Es popular por su simplicidad y eficiencia.\n", " \n", "3. **GraphQL:**\n", " - Permite consultas más granulares y controladas.\n", " - Envía un JSON con la consulta en la URL.\n", " - Independiente del protocolo HTTP.\n", " - Utiliza un lenguaje de definición de esquemas (IDL).\n", "\n", "4. **gRPC (gRPC Remote Procedure Calls):**\n", " - Utiliza HTTP/2, permitiendo una comunicación más eficiente.\n", " - Ofrece soporte para múltiples lenguajes de programación.\n", "\n", "##### API REST\n", "\n", "Las APIs REST utilizan las peticiones HTTP como verbos del protocolo:\n", "\n", "- **GET:** Recuperar recursos.\n", "- **POST:** Crear nuevos recursos.\n", "- **PUT:** Actualizar recursos existentes.\n", "- **DELETE:** Eliminar recursos.\n", "- **PATCH:** Actualizar parcialmente recursos.\n", "\n", "**Características de las APIs REST:**\n", "\n", "- Utilizan rutas URL para identificar los recursos.\n", "- Los códigos de respuesta HTTP indican el estado de la solicitud (por ejemplo, 200 para éxito, 404 para no encontrado, 500 para error del servidor).\n", "- Los datos (payload) pueden enviarse en XML o JSON.\n", "- Una API que sigue estrictamente las características REST se denomina RESTful.\n", "\n", "**Ejemplo de API REST:**\n", "\n", "Supongamos una API para gestionar una colección de libros:\n", "\n", "- `GET /books`: Recupera la lista de libros.\n", "- `POST /books`: Crea un nuevo libro.\n", "- `GET /books/:id`: Recupera un libro específico.\n", "- `PUT /books/:id`: Actualiza un libro específico.\n", "- `DELETE /books/:id`: Elimina un libro específico.\n", "\n", "##### API GraphQL\n", "\n", "GraphQL es una alternativa a REST que permite realizar consultas más precisas y específicas. En lugar de varias URLs, una sola URL acepta consultas en JSON.\n", "\n", "**Características de las APIs GraphQL:**\n", "\n", "- Permiten más control y granularidad en las consultas.\n", "- Las peticiones son fáciles de entender y leer para los humanos.\n", "- No están limitadas a HTTP.\n", "- Utilizan el IDL (Schema Definition Language) para definir el esquema.\n", "\n", "**Ejemplo de API GraphQL:**\n", "\n", "Consulta para obtener el título y autor de un libro específico:\n", "\n", "```graphql\n", "{\n", " book(id: \"1\") {\n", " title\n", " author\n", " }\n", "}\n", "```\n", "\n", "##### SDKs (Kits de Desarrollo de Software)\n", "\n", "Las APIs pueden ser complejas, y herramientas como Firebase, MongoDB Realm, y Supabase ofrecen SDKs que simplifican tareas comunes como la autenticación de usuarios y las consultas avanzadas.\n", "\n", "**Características de los SDKs:**\n", "\n", "- Facilitan la interacción con las APIs al proporcionar bibliotecas preconstruidas.\n", "- Ahorra tiempo en la programación de la comunicación entre el cliente y el servidor.\n", "- No son estándar y dependen del proveedor del servicio.\n", "\n", "> **Nota:** Aunque los SDKs pueden simplificar mucho el trabajo, en este curso evitaremos su uso para centrarnos en aprender los fundamentos de las APIs.\n", "\n", "\n", "### Webs SPA (Single Page Application)\n", "\n", "Las aplicaciones de una sola página (SPA) utilizan AJAX para cargar y actualizar contenido sin necesidad de recargar la página completa. Esto permite crear aplicaciones web más rápidas y con una experiencia de usuario similar a las aplicaciones de escritorio.\n", "\n", "#### Ejemplo de SPA con AJAX\n", "\n", "```html\n", "\n", "\n", "\n", " SPA Example\n", " \n", "\n", "\n", " \n", "
\n", "\n", " \n", "\n", "\n", "```\n", "\n", "En este ejemplo, cada botón en la navegación carga contenido diferente en el `div` con el ID \"content\" utilizando AJAX. Esto permite que la página se actualice dinámicamente sin necesidad de recargarla por completo.\n", "\n", "\n", "### XMLHttpRequest en JavaScript\n", "\n", "XMLHttpRequest (XHR) es una API utilizada para enviar y recibir datos entre un cliente web y un servidor. A pesar de su nombre, XMLHttpRequest puede manejar diferentes tipos de datos, aunque en este capítulo nos centraremos principalmente en JSON debido a su popularidad en las aplicaciones web modernas.\n", "\n", "#### Inicialización y Uso Básico\n", "\n", "Para comenzar a utilizar XMLHttpRequest, primero debemos crear una instancia del objeto `XMLHttpRequest`.\n", "\n", "```javascript\n", "var req = new XMLHttpRequest();\n", "```\n", "\n", "Una vez creado el objeto, debemos configurar la solicitud utilizando el método `open`. Este método tiene tres parámetros principales:\n", "1. **Método HTTP**: El método de la solicitud, como 'GET' o 'POST'.\n", "2. **URL**: La URL a la que se envía la solicitud.\n", "3. **Asíncrono**: Un valor booleano que indica si la solicitud debe ser asíncrona (`true`) o síncrona (`false`). En la mayoría de los casos, queremos que sea asíncrona para no bloquear la ejecución del script.\n", "\n", "```javascript\n", "req.open('GET', 'http://www.mozilla.org/', true);\n", "```\n", "\n", "XMLHttpRequest tiene un conjunto de estados que indican el progreso de la solicitud. Estos estados están representados por la propiedad `readyState` del objeto XHR. Los posibles valores de `readyState` son:\n", "- `0` (UNSENT): La solicitud no ha sido inicializada.\n", "- `1` (OPENED): Se ha establecido la conexión con el servidor.\n", "- `2` (HEADERS_RECEIVED): Se han recibido los encabezados de la respuesta.\n", "- `3` (LOADING): El cuerpo de la respuesta se está recibiendo.\n", "- `4` (DONE): La solicitud se ha completado y la respuesta está lista.\n", "\n", "Para realizar alguna acción cuando la solicitud cambie de estado, se utiliza la propiedad `onreadystatechange`, que se asigna a una función. Esta función se ejecutará cada vez que cambie el estado de la solicitud.\n", "\n", "```javascript\n", "req.onreadystatechange = function (aEvt) {\n", " if (req.readyState == 4) {\n", " if (req.status == 200) {\n", " console.log(req.responseText);\n", " } else {\n", " console.log(\"Error loading page\\n\");\n", " }\n", " }\n", "};\n", "```\n", "\n", "Finalmente, enviamos la solicitud al servidor utilizando el método `send`. Si estamos enviando datos (por ejemplo, en una solicitud `POST`), estos se pasan como argumento a `send`. En una solicitud `GET`, simplemente pasamos `null`.\n", "\n", "```javascript\n", "req.send(null);\n", "```\n", "\n", "#### Ejemplo Completo\n", "\n", "```javascript\n", "var req = new XMLHttpRequest();\n", "req.open('GET', 'http://www.mozilla.org/', true);\n", "req.onreadystatechange = function (aEvt) {\n", " if (req.readyState == 4) {\n", " if (req.status == 200) {\n", " console.log(req.responseText);\n", " } else {\n", " console.log(\"Error loading page\\n\");\n", " }\n", " }\n", "};\n", "req.send(null);\n", "```\n", "\n", "1. **Crear el Objeto XHR**:\n", " ```javascript\n", " var req = new XMLHttpRequest();\n", " ```\n", " Aquí se crea una nueva instancia del objeto `XMLHttpRequest`.\n", "\n", "2. **Configurar la Solicitud**:\n", " ```javascript\n", " req.open('GET', 'http://www.mozilla.org/', true);\n", " ```\n", " Se configura la solicitud para hacer una petición `GET` a la URL especificada. El tercer parámetro, `true`, indica que la solicitud debe ser asíncrona.\n", "\n", "3. **Monitorear Cambios de Estado**:\n", " ```javascript\n", " req.onreadystatechange = function (aEvt) {\n", " if (req.readyState == 4) {\n", " if (req.status == 200) {\n", " console.log(req.responseText);\n", " } else {\n", " console.log(\"Error loading page\\n\");\n", " }\n", " }\n", " };\n", " ```\n", " Se define una función que se ejecuta cada vez que cambia el estado de la solicitud. Cuando `readyState` es `4`, significa que la solicitud se ha completado. Si `status` es `200`, significa que la solicitud fue exitosa y se imprime la respuesta en la consola. Si el estado es diferente, se imprime un mensaje de error.\n", "\n", "4. **Enviar la Solicitud**:\n", " ```javascript\n", " req.send(null);\n", " ```\n", " Finalmente, se envía la solicitud al servidor.\n", "\n", "\n", "> Este capítulo ha cubierto los conceptos básicos y la implementación de XMLHttpRequest. En capítulos posteriores, exploraremos métodos modernos como `fetch` y la forma en que se integran con las características más recientes de JavaScript, como las promesas y la sintaxis `async/await`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fetch\n", "\n", "La función `fetch` de JavaScript proporciona una manera sencilla y poderosa de realizar solicitudes HTTP. Es similar a `XMLHttpRequest` (XHR), pero utiliza promesas y tiene una sintaxis más moderna y limpia.\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "\n", "(()=>{\n", "fetch('http://127.0.0.1:5500/datos.json')\n", " .then(\n", " function(response) {\n", " if (response.status !== 200) {\n", " console.log('Looks like there was a problem. Status Code: ' + response.status);\n", " return; \n", " }\n", " response.json().then(function(data) {\n", " console.log(data);\n", " });\n", " }\n", " )\n", " .catch(function(err) {\n", " console.log('Fetch Error : ', err);\n", " });\n", "})();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "En este ejemplo, `fetch` solicita un archivo JSON. Si la respuesta tiene un estado diferente de 200 (OK), se imprime un mensaje de error. Si la respuesta es correcta, se convierte a JSON y se imprime.\n", "\n", "### Objeto Response\n", "\n", "Si la solicitud tiene éxito, `fetch` devuelve un objeto `Response`, que es un flujo (stream) con varias propiedades y métodos útiles.\n", "\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Fetch Error : TypeError: error sending request for url (http://127.0.0.1:5500/datos.json): error trying to connect: tcp connect error: Connection refused (os error 111)\n", " at async mainFetch (ext:deno_fetch/26_fetch.js:170:12)\n", " at async fetch (ext:deno_fetch/26_fetch.js:391:7)\n" ] } ], "source": [ "(()=>{\n", "fetch('https://github.com/').then(function(response) {\n", " console.log(response.headers.get('Content-Type'));\n", " console.log(response.headers.get('Date'));\n", " console.log(response.status);\n", " console.log(response.statusText);\n", " console.log(response.type);\n", " console.log(response.url);\n", "});\n", "})();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "Este ejemplo muestra cómo acceder a diferentes propiedades del objeto `Response`, como los encabezados y el estado de la solicitud.\n", "\n", "#### Guardar los Datos\n", "\n", "`fetch` permite obtener el texto o un objeto de la respuesta. Las funciones `response.json()` y `response.text()` devuelven promesas que se resuelven con el contenido adecuado. No es posible usar ambas funciones en una misma petición.\n" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "https://github.com/\n" ] } ], "source": [ "\n", "(()=>{\n", "fetch(\"https://dwec-daw-default-rtdb.firebaseio.com/productos.json\")\n", " .then(response => response.json())\n", " .then(data => console.log(data));\n", "\n", "fetch(\"https://dwec-daw-default-rtdb.firebaseio.com/productos.json\")\n", " .then(response => response.text())\n", " .then(data => console.log(data));\n", "})();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "En estos ejemplos, se hace una solicitud a una URL y se procesan los datos como JSON en el primer caso y como texto en el segundo.\n", "\n", "### Encadenar Promesas\n", "\n", "Es posible encadenar promesas para manejar el flujo de la solicitud de manera más estructurada.\n", "\n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"error\" : \"Permission denied\"\n", "}\n", "\n" ] } ], "source": [ "(()=>{\n", "function showStatus(response) {\n", " if (response.status >= 200 && response.status < 300) {\n", " return Promise.resolve(response);\n", " } else {\n", " return Promise.reject(new Error(response.statusText));\n", " }\n", "}\n", "\n", "function json(response) { \n", " return response.json(); \n", "}\n", "\n", "fetch('datos.json')\n", " .then(showStatus)\n", " .then(json)\n", " .then(function(data) {\n", " console.log('Request succeeded with JSON response', data);\n", " }).catch(function(error) {\n", " console.log('Request failed: ', error);\n", " });\n", " })();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "En este ejemplo, la función `status` verifica si la respuesta es correcta, y la función `json` convierte la respuesta en un objeto JSON. Luego, se manejan los datos o se capturan errores según corresponda.\n", "\n", "### Enviar Datos con Fetch\n", "\n", "#### Usar el método POST\n", "\n", "Para enviar datos a un servidor, se puede usar el método `POST` con `fetch`.\n", "\n", "```javascript\n", "fetch(url, {\n", " method: 'post',\n", " headers: {\n", " \"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n", " },\n", " body: 'foo=bar&lorem=ipsum'\n", " })\n", " .then(response => response.json())\n", " .then(function (data) {\n", " console.log('Request succeeded with JSON response', data);\n", " })\n", " .catch(function (error) {\n", " console.log('Request failed', error);\n", " });\n", "```\n", "\n", "En este ejemplo, se envían datos codificados en la URL (formato de formulario) al servidor.\n", "\n", "#### Enviar JSON\n", "\n", "Para enviar datos en formato JSON, se debe configurar el encabezado `Content-Type` y convertir el objeto de datos a JSON.\n", "\n", "```javascript\n", "let datos = {username: 'example'};\n", "\n", "fetch(url, {\n", " method: 'post',\n", " headers: {\n", " \"Content-type\": \"application/json; charset=UTF-8\"\n", " },\n", " body: JSON.stringify(datos)\n", " })\n", " .then(response => response.json())\n", " .then(function (data) {\n", " console.log('Request succeeded with JSON response', data);\n", " })\n", " .catch(function (error) {\n", " console.log('Request failed', error);\n", " });\n", "```\n", "\n", "En este ejemplo, un objeto JavaScript se convierte a JSON y se envía al servidor.\n", "\n", "### Uso de FormData\n", "\n", "`FormData` es un objeto predefinido en JavaScript que se utiliza para crear pares clave-valor para enviar formularios mediante `XMLHttpRequest` o `fetch`.\n", "\n", "```javascript\n", "let formElement = document.getElementById(\"myFormElement\"); // Un formulario HTML\n", "let formData = new FormData(formElement); // Constructor de FormData con un formulario\n", "\n", "formData.append(\"serialnumber\", serialNumber++); // Añadir más datos\n", "formData.append(\"afile\", fileInputElement.files[0]); // Añadir un archivo\n", "\n", "fetch('http://localhost:3000/upload', { method: 'POST', body: formData });\n", "```\n", "\n", "Este ejemplo muestra cómo crear un objeto `FormData` a partir de un formulario HTML y enviar datos adicionales, incluyendo un archivo, al servidor.\n", "\n", "#### Convertir FormData a JSON\n", "\n", "Para enviar `FormData` como JSON, se puede convertir a un objeto JavaScript y luego a una cadena JSON.\n", "\n", "```javascript\n", "let data = new FormData(form);\n", "let body = JSON.stringify(Object.fromEntries(data));\n", "\n", "return fetch(url, {\n", " method: 'POST',\n", " headers: {\n", " \"Content-type\": \"application/json; charset=UTF-8\"\n", " },\n", " body\n", "}).then(response => response.json());\n", "```\n", "\n", "En este ejemplo, se convierte `FormData` en un objeto JSON antes de enviarlo.\n", "\n", "### Cargar Imágenes en Segundo Plano\n", "\n", "Es posible cargar imágenes en segundo plano utilizando `fetch` y el método `blob`.\n", "\n", "```javascript\n", "\"${name}\"\n", "\n", "fetch(image_url)\n", ".then(response => response.status == 200 ? response : Promise.reject(response.status))\n", ".then(response => response.blob())\n", ".then(imageBlob => {\n", " let imageURL = URL.createObjectURL(imageBlob);\n", " document.querySelector('img').src = imageURL;\n", "})\n", ".catch(error => console.log(error));\n", "```\n", "\n", "Este ejemplo muestra cómo cargar una imagen en segundo plano y actualizar la fuente de una etiqueta `` con la URL del `blob` de la imagen.\n", "\n", "La función URL.createObjectURL(blob) es un método del API de URL en JavaScript que permite crear una URL temporal, de tipo \"blob\", que representa un objeto de datos (Blob o File) en el navegador. Esta URL puede ser utilizada para acceder y manipular el contenido del objeto de datos como si fuera un archivo disponible en una URL normal. \n", "\n", "`URL.createObjectURL(blob)` crea una URL única que representa el objeto `Blob` (o `File`). Esta URL es válida mientras el documento que la creó esté en memoria, y se puede usar como referencia al contenido del objeto de datos. La URL generada permite acceder y manipular el contenido del `Blob` como si fuera un archivo remoto. La URL no requiere que los datos sean enviados a un servidor; todo se maneja localmente en el navegador. La URL generada puede ser asignada a elementos HTML, como ``, `