Capítulo 4. Solicitudes de red
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
Introducción
Te resultaría difícil encontrar hoy en día una aplicación web que no envíe peticiones a la red, en . Desde los albores de la Web 2.0 y el novedoso enfoque conocido como Ajax (Asynchronous JavaScript and XML), las aplicaciones web han estado enviando peticiones asíncronas para obtener nuevos datos sin recargar toda la página. La API XMLHttpRequest inició una nueva era de aplicaciones JavaScript interactivas. A pesar de su nombre, XMLHttpRequest (o XHR, como se conoce a veces) también puede funcionar con JSON y cargas útiles de datos de formulario.
XMLHttpRequest supuso un cambio en las reglas del juego, pero puede resultar complicado trabajar con la API. Con el tiempo, bibliotecas de terceros, como Axios y jQuery, añadieron API más ágiles que envolvían la API XHR central.
En 2015, una nueva API basada en Promise
llamada Fetch se convirtió en un nuevo estándar, y los navegadores empezaron gradualmente a añadir soporte para ella. Hoy en día, Fetch es la forma estándar de realizar peticiones asíncronas desde tus aplicaciones web.
Este capítulo explora XHR y Fetch, así como algunas otras API para lacomunicación en red:
- Balizas
-
Una simple petición POST unidireccional ideal para enviar datos analíticos
- Eventos enviados por el servidor
-
Una conexión persistente unidireccional con un servidor para recibir eventos en tiempo real
- WebSockets
-
Una conexión bidireccional persistente para la comunicación bidireccional
Enviar una petición con XMLHttpRequest
Solución
Utiliza la API XMLHttpRequest. XMLHttpRequest es una API asíncrona, basada en eventos, para hacer peticiones a la red. El uso general de XMLHttpRequest es el siguiente:
-
Crea un nuevo objeto
XMLHttpRequest
. -
Añade un receptor para el evento
load
, que recibe los datos de la respuesta. -
Llama a
open
sobre la solicitud, pasando el método HTTP y la URL. -
Por último, llama a
send
sobre la petición. Esto activa el envío de la solicitud HTTP.
El Ejemplo 4-1 muestra un ejemplo sencillo de cómo trabajar con datos JSON utilizando un XHR.
Ejemplo 4-1. Hacer una petición GET con XMLHttpRequest
/**
* Loads user data from the URL /api/users, then prints them
* to the console
*/
function
getUsers
()
{
const
request
=
new
XMLHttpRequest
();
request
.
addEventListener
(
'load'
,
event
=>
{
// The event target is the XHR itself; it contains a
// responseText property that we can use to create a JavaScript object from
// the JSON text.
const
users
=
JSON
.
parse
(
event
.
target
.
responseText
);
console
.
log
(
'Got users:'
,
users
);
});
// Handle any potential errors with the request.
// This only handles network errors. If the request
// returns an error status like 404, the 'load' event still fires
// where you can inspect the status code.
request
.
addEventListener
(
'error'
,
err
=>
{
console
.
log
(
'Error!'
,
err
);
});
request
.
open
(
'GET'
,
'/api/users'
);
request
.
send
();
}
Debate
La API XMLHttpRequest es una API basada en eventos. Cuando se recibe la respuesta, se activa un evento load
. En el Ejemplo 4-1, el controlador de eventosload
pasa el texto sin procesar de la respuesta a JSON.parse
. Espera que el cuerpo de la respuesta sea JSON y utiliza JSON.parse
para convertir la cadena JSON en un objeto.
Si se produce un error al cargar los datos, se activa el evento error
. Esto gestiona los errores de conexión o de red, pero un código de estado HTTP que se considere un "error", como 404 o 500, no desencadena este evento. En su lugar, también desencadena el evento load
.
Para protegerte de tales errores, debes examinar la propiedad status
de la respuesta para determinar si existe tal situación de error. Puedes acceder a ella haciendo referencia a event.target.status
.
Fetch es compatible desde hace mucho tiempo, así que, a menos que tengas que dar soporte a navegadores realmente antiguos, lo más probable es que no necesites utilizar XMLHttpRequest. La mayoría de las veces -si no todas- utilizarás la API Fetch.
Enviar una solicitud GET con la API Fetch
Solución
Utiliza la API Fetch. Fetch es una API de petición más reciente que utiliza Promise
s. Es muy flexible y puede enviar todo tipo de datos, pero el Ejemplo 4-2 envía una petición GET básica a una API.
Ejemplo 4-2. Enviar una solicitud GET con la API Fetch
/**
* Loads users by calling the /api/users API, and parses the
* response JSON.
* @returns a Promise that resolves to an array of users returned by the API
*/
function
loadUsers
()
{
// Make the request.
return
fetch
(
'/api/users'
)
// Parse the response body to an object.
.
then
(
response
=>
response
.
json
())
// Handle errors, including network and JSON parsing errors.
.
catch
(
error
=>
console
.
error
(
'Unable to fetch:'
,
error
.
message
));
}
loadUsers
().
then
(
users
=>
{
console
.
log
(
'Got users:'
,
users
);
});
Debate
La API Fetch es más concisa. Devuelve un Promise
que resuelve a un objeto que representa la respuesta HTTP. El objeto response
contiene datos como el código de estado, las cabeceras y el cuerpo.
Para obtener el cuerpo de la respuesta JSON, tienes que llamar al método json
de la respuesta. Este método lee el cuerpo del flujo y devuelve un Promise
que resuelve el cuerpo JSON analizado como un objeto. Si el cuerpo de la respuesta no es JSON válido, se rechaza el Promise
.
La respuesta también tiene métodos para leer el cuerpo en otros formatos, como FormData
o una cadena de texto sin formato.
Como Fetch funciona con Promise
s, también puedes utilizar await
, como se muestra en el Ejemplo 4-3.
Ejemplo 4-3. Utilizar Fetch con async
/await
async
function
loadUsers
()
{
try
{
const
response
=
await
fetch
(
'/api/users'
);
return
response
.
json
();
}
catch
(
error
)
{
console
.
error
(
'Error loading users:'
,
error
);
}
}
async
function
printUsers
()
{
const
users
=
await
loadUsers
();
console
.
log
(
'Got users:'
,
users
);
}
Enviar una solicitud POST con la API Fetch
Solución
Utiliza la API Fetch, especificando el método (POST), y el cuerpo y tipo de contenido JSON (ver Ejemplo 4-4).
Ejemplo 4-4. Envío de carga útil JSON mediante POST con la API Fetch
/**
* Creates a new user by sending a POST request to /api/users.
* @param firstName The user's first name
* @param lastName The user's last name
* @param department The user's department
* @returns a Promise that resolves to the API response body
*/
function
createUser
(
firstName
,
lastName
,
department
)
{
return
fetch
(
'/api/users'
,
{
method
:
'POST'
,
body
:
JSON
.
stringify
({
firstName
,
lastName
,
department
}),
headers
:
{
'Content-Type'
:
'application/json'
}
})
.
then
(
response
=>
response
.
json
());
}
createUser
(
'John'
,
'Doe'
,
'Engineering'
)
.
then
(()
=>
console
.
log
(
'Created user!'
))
.
catch
(
error
=>
console
.
error
(
'Error creating user:'
,
error
));
Debate
El ejemplo 4-4 envía algunos datos JSON en una petición POST
. Llamar a JSON.stringify
sobre el objeto usuario lo convierte en una cadena JSON, lo que es necesario para enviarlo como cuerpo con fetch
. También necesitas establecer la cabecera Content-Type
para que el servidor sepa cómo interpretar el cuerpo.
Fetch también te permite enviar otros tipos de contenido como cuerpo. El Ejemplo 4-5 muestra cómo enviarías una petición a POST
con algunos datos de formulario.
Ejemplo 4-5. Enviar datos de formulario en una petición POST
fetch
(
'/login'
,
{
method
:
'POST'
,
body
:
'username=sysadmin&password=password'
,
headers
:
{
'Content-Type'
:
'application/x-www-form-urlencoded;charset=UTF-8'
}
})
.
then
(
response
=>
response
.
json
())
.
then
(
data
=>
console
.
log
(
'Logged in!'
,
data
))
.
catch
(
error
=>
console
.
error
(
'Request failed:'
,
error
));
Cargar un archivo con la API Fetch
Solución
Utiliza un elemento <input type="file">
, y envía el contenido del archivo como cuerpo de la petición (consulta el Ejemplo 4-6).
Ejemplo 4-6. Enviar datos de archivos con la API Fetch
/**
* Given a form with a 'file' input, sends a POST request containing
* the file data in its body.
* @param form the form object (should have a file input with the name 'file')
* @returns a Promise that resolves when the response JSON is received
*/
function
uploadFile
(
form
)
{
const
formData
=
new
FormData
(
form
);
const
fileData
=
formData
.
get
(
'file'
);
return
fetch
(
'https://httpbin.org/post'
,
{
method
:
'POST'
,
body
:
fileData
})
.
then
(
response
=>
response
.
json
());
}
Debate
No hay muchos pasos para subir un archivo utilizando las APIs de los navegadores modernos. La página <input type="file">
proporciona los datos del archivo a través de la API FormData y se incluyen en el cuerpo de la solicitud POST. El navegador se encarga del resto.
Enviar una baliza
Solución
Utiliza la Beacon API para enviar datos en una solicitud POST. Una solicitud POST normal con la API Fetch puede no completarse a tiempo antes de que se descargue la página. Utilizar una baliza tiene más probabilidades de éxito (ver Ejemplo 4-7). El navegador no espera una respuesta, y es más probable que la solicitud tenga éxito si se envía cuando el usuario está abandonando tu sitio.
Ejemplo 4-7. Enviar una baliza
const
currentUser
=
{
username
:
'sysadmin'
};
// Some analytics data we want to capture
const
data
=
{
user
:
currentUser
.
username
,
lastVisited
:
new
Date
()
};
// Send the data before unload.
document
.
addEventListener
(
'visibilitychange'
,
()
=>
{
// If the visibility state is 'hidden', that means the page just became hidden.
if
(
document
.
visibilityState
===
'hidden'
)
{
navigator
.
sendBeacon
(
'/api/analytics'
,
data
);
}
});
Debate
Con una llamada a XMLHttpRequest
o fetch
, el navegador espera la respuesta y la devuelve (con un evento o Promise
). En general, no necesitas esperar la respuesta para peticiones unidireccionales, como el envío de datos analíticos.
En lugar de un Promise
, navigator.sendBeacon
devuelve un valor booleano que indica si se programó la operación de envío. No hay más eventos ni notificaciones.
navigator.sendBeacon
siempre envía una petición POST
. Si quieres enviar varios conjuntos de datos analíticos, como una colección de interacciones de interfaz de usuario, puedes recopilarlos en una matriz a medida que el usuario interactúa con tu página, y luego enviar la matriz como cuerpo de POST
con la baliza .
Escucha de Eventos Remotos con Eventos Enviados por el Servidor
Solución
Utiliza la API EventSource para recibir eventos enviados por el servidor (SSE).
Para empezar a escuchar SSE, crea una nueva instancia de EventSource
, pasando la URL como primer argumento (ver Ejemplo 4-8).
Ejemplo 4-8. Abrir una conexión SSE
const
events
=
new
EventSource
(
'https://example.com/events'
);
// Fired once connected
events
.
addEventListener
(
'open'
,
()
=>
{
console
.
log
(
'Connection is open'
);
});
// Fired if a connection error occurs
events
.
addEventListener
(
'error'
,
event
=>
{
console
.
log
(
'An error occurred:'
,
event
);
});
// Fired when receiving an event with a type of 'heartbeat'
events
.
addEventListener
(
'heartbeat'
,
event
=>
{
console
.
log
(
'got heartbeat:'
,
event
.
data
);
});
// Fired when receiving an event with a type of 'notice'
events
.
addEventListener
(
'notice'
,
event
=>
{
console
.
log
(
'got notice:'
,
event
.
data
);
})
// The EventSource leaves the connection open. If we want to close the connection,
// we need to call close on the EventSource object.
function
cleanup
()
{
events
.
close
();
}
Debate
Un EventSource
debe conectarse a un punto final HTTP especial que deja la conexión abierta con una cabecera Content-Type
de text/event-stream
. Cada vez que se produce un evento, el servidor puede enviar un nuevo mensaje a través de la conexión abierta.
Nota
Como señala MDN, es muy recomendable utilizar HTTP/2 con SSE. De lo contrario, los navegadores imponen un límite estricto al número de conexiones EventSource
por dominio. En este caso, sólo puede haber hasta seis conexiones.
Este límite no es por pestaña; se impone en todas las pestañas del navegador de un dominio determinado.
Cuando EventSource
recibe un evento a través de una conexión persistente, es texto sin formato. Puedes acceder al texto del evento desde la propiedad data
del objeto de evento recibido. Aquí tienes un ejemplo de un evento del tipo notice
:
event: notice data: Connection established at 10:51 PM, 2023-04-22 id: 3
Para escuchar este evento, llama a addEventListener('notice')
en el objeto EventSource
. El objeto evento tiene una propiedad data
, cuyo valor es cualquier valor de cadena prefijado con data:
en el evento.
Si un evento no tiene un tipo de evento, puedes escuchar el evento genérico message
para recibirlo .
Intercambiar datos en tiempo real con WebSockets
Solución
Utiliza la API WebSocket para abrir una conexión persistente con tu servidor backend (consulta el Ejemplo 4-9).
Ejemplo 4-9. Crear una conexión WebSocket
// Open the WebSocket connection (the URL scheme should be ws: or wss:).
const
socket
=
new
WebSocket
(
url
);
socket
.
addEventListener
(
'open'
,
onSocketOpened
);
socket
.
addEventListener
(
'message'
,
handleMessage
);
socket
.
addEventListener
(
'error'
,
handleError
);
socket
.
addEventListener
(
'close'
,
onSocketClosed
);
function
onSocketOpened
()
{
console
.
log
(
'Socket ready for messages'
);
}
function
handleMessage
(
event
)
{
console
.
log
(
'Received message:'
,
event
.
data
);
}
function
handleError
(
event
)
{
console
.
log
(
'Socket error:'
,
event
);
}
function
onSocketClosed
()
{
console
.
log
(
'Connection was closed'
);
}
Nota
Para utilizar WebSockets, tu servidor debe tener un punto final habilitado para WebSocket al que puedas conectarte. MDN tiene un buen artículo sobre cómo crear un servidor WebSocket.
Una vez que el socket dispara el evento open
, puedes empezar a enviar mensajes, como se muestra en el Ejemplo 4-10.
Ejemplo 4-10. Envío de mensajes WebSocket
// Messages are simple strings.
socket
.
send
(
'Hello'
);
// The socket needs the data as a string, so you can use
// JSON.stringify to serialize objects to be sent.
socket
.
send
(
JSON
.
stringify
({
username
:
'sysadmin'
,
password
:
'password'
}));
Una conexión WebSocket es una conexión bidireccional. Los datos recibidos del servidor disparan un evento message
. Puedes manejarlos como necesites o incluso enviar una respuesta (ver Ejemplo 4-11).
Ejemplo 4-11. Responder a un mensaje WebSocket
socket
.
addEventListener
(
'message'
,
event
=>
{
socket
.
send
(
'ACKNOWLEDGED'
);
});
Por último, para limpiar cuando hayas terminado, puedes cerrar la conexión llamando a close
en el objeto WebSocket.
Debate
Los WebSockets son muy adecuados para aplicaciones que requieren capacidades en tiempo real, como un sistema de chat o de monitoreo de eventos. Los puntos finales de WebSocket tienen un esquema ws://
o wss://
. Son análogos a http://
y https://
-uno es inseguro y el otro utilizaencriptación-.
Para iniciar una conexión WebSocket, el navegador envía primero una solicitud GET
al punto final WebSocket. La carga útil de la solicitud de la URL wss://example.com/websocket
tiene el siguiente aspecto:
GET /websocket HTTP/1.1 Host: example.com Sec-WebSocket-Key: aSBjYW4gaGFzIHdzIHBsej8/ Sec-WebSocket-Version: 13 Connection: Upgrade Upgrade: websocket
Esto inicia un intercambio WebSocket. Si tiene éxito, el servidor responde con un código de estado 101 (Cambio de protocolo):
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: bm8gcGVla2luZywgcGxlYXNlIQ==
El protocolo WebSocket especifica un algoritmo para generar una cabecera Sec-Websocket-Accept
basada en el Sec-WebSocket-Key
de la solicitud. El cliente verifica este valor, y en ese momento la conexión WebSocket bidireccional está activa y el socket dispara el evento open
.
Una vez abierta la conexión, puedes escuchar mensajes con el evento message
y enviar mensajes llamando a send
sobre el objeto socket. Después, puedes terminar la sesión WebSocket llamando a close
sobre el objeto socket.
Get Libro de recetas de la API web now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.