Seleccionar página

Cuando apareció XMLHttpRequest se produjo un importante paso adelante en el desarrollo web, siendo rápidamente acogido por todos los fabricantes. Fue, sin duda, un importante hito en el desarrollo de la web, abriendo el camino hacia un desarrollo lleno de llamadas HTTP. Aunque han aparecido librerías y utilidades para simplificar el uso de este API, lo cierto es que se ha ido quedando atrás y no da respuesta a mucho de los retos que nos encontramos hoy en día.

El API fetch es un nuevo estándar que viene a dar una alternativa para interactuar por HTTP, con un diseño moderno, basado en promesas, con mayor flexibilidad y capacidad de control a la hora de realizar llamadas al servidor, y que funciona tanto desde window como desde worker, lo cual es cada día más importante. También está disponible en Node, por lo que podemos utilizarlo de forma isomórfica, es decir, tanto en cliente como en servidor.

Este es un estándar publicado por WHATWG (Web Hypertext Application Technology Working Group), los responsables de algunos de los estándares más interesantes que se han publicado en los últimos tiempos. El modelo de estandarización es bastante más dinámico que el del W3C y los fabricantes están adoptando sus propuestas con bastante rapidez.

el API Fetch está soportado de forma nativa por Edge 14, Firefox 39, Chrome 42 y Opera 29. Para Internet Explorer 10 o Safari 6.1 o superiores o versiones anteriores de Firefox o Chrome, podemos utilizar el polyfill publicado por Github https://github.com/github/fetch. Hay una bastante buena librería para Node: node-fetch https://www.npmjs.com/package/node-fetch, aunque tiene algunas pequeñas limitaciones, están trabajando en una versión 2 que cumple el estándar con más precisión.

Vamos a dar un breve repaso a el API fetch:

Funcionamiento básico

Una de las características más importantes del API fetch es que utiliza promesas, es decir, devuelve un objeto con dos métodos, uno then() y otro catch() a la que pasaremos una función que será invocada cuando se obtenga la respuesta o se produzca un error.

Aquí hay que aclarar un punto con los errores: si se devuelve un código HTTP correspondiente a un error no se ejecutará el catch(), ya que se ha obtenido una respuesta válida, por lo que se ejecutará el then(). Sólo si hay un error de red o de otro tipo se ejecutará el catch().

Otro aspecto importante que hay que comprender es que para obtener el body o cuerpo del mensaje devuelto por el servidor deberemos obtener una segunda promesa por medio de los métodos del objeto Response. Por ello será muy habitual ver dos promesas encadenadas, una para el fetch() y otra con el retorno del método que utilicemos para obtener el body.

En los ejemplos vamos a utilizar las opciones que nos ofrece https://httpbin.org/ para comprobar el funcionamiento de nuestros clientes HTTP.

Vamos a verlo paso a paso:

fetch('https://httpbin.org/ip')
    .then(function(response) {
        return response.text();
    })
    .then(function(data) {
        console.log('data = ', data);
    })
    .catch(function(err) {
        console.error(err);
    });

ejecutar…

¿Qué hemos hecho?:

  • hemos llamado a fetch() con la URL a la que queremos acceder como parámetro
  • esta llamada nos devuelve una promesa
  • el método then() de esa promesa nos entrega un objeto response
  • del objeto response llamamos al método text() para obtener el cuerpo retornado en forma de texto
  • nos devuelve otra promesa que se resolverá cuando se haya obtenido el contenido
  • el método then() de esa promesa recibe el cuerpo devuelto por el servidor en formato de texto
  • hemos incluido un catch() por si se produce algún error

Opciones de fetch()

Quizás no nos hayamos dado cuenta, pero en el ejemplo anteriores no hemos indicado que método teníamos que utilizar, simplemente hemos pasado la URL y se considera que queremos utilizar el método GET que es el valor por defecto. La forma de configurar esta llamada es utilizar el segundo parámetro de fetch(), donde pasaremos un objeto con las opciones.

En este nuevo ejemplo vamos a llamar a https://httpbin.org/post, un servicio que nos va a devolver un JSON con lo que enviemos con un método POST y alguna información adicional.

fetch('https://httpbin.org/post', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: 'a=1&b=2'
    })
    .then(function(response) {
        console.log('response =', response);
        return response.json();
    })
    .then(function(data) {
        console.log('data = ', data);
    })
    .catch(function(err) {
        console.error(err);
    });

ejecutar…

Como podemos ver, hemos configurado el método, una cabecera y el cuerpo de la llamada.

Vamos a ver una variante donde se envían datos en formato JSON y se indica que no se utilice la caché.

fetch('https://httpbin.org/post',{
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({"a": 1, "b": 2}),
        cache: 'no-cache'
    })
    .then(function(response) {
        return response.json();
    })
    .then(function(data) {
        console.log('data = ', data);
    })
    .catch(function(err) {
        console.error(err);
    });

ejecutar…

Las opciones que podemos configurar son:

  • method: método a utilizar
  • headers: cabeceras que se deben enviar (ver objeto Headers).
  • body: cuerpo que se envía al servidor, que puede ser una cadena, un objecto Blob, BufferSource, FormData o URLSearchParams.
  • mode: modo del la solicitud: ‘cors’, ‘no-cors’, ‘same-origin’, ‘navigate’.
  • credentials: credenciales utilizadas: ‘omit’, ‘same-origin’, ‘include’.
  • cache: forma de utilización de la caché: ‘default’, ‘no-store’, ‘reload’, ‘no-cache’, ‘force-cache’, ‘only-if-cached’.
  • redirect: forma de gestionar la redirección: ‘follow’, ‘error’, ‘manual’.
  • referrer: valor utilizado como referrer: ‘client’, ‘no-referrer’ una URL.
  • referrerPolicy: especifica el valor de la cabecera referer: ‘no-referrer’, ‘no-referrer-when-downgrade’, ‘origin’, ‘origin-when-cross-origin’, ‘unsafe-url’.
  • integrity: valor de integridad de la solicitud.

Response

En la función que pasamos a then() vamos a recibir un objeto Response. Este objeto contiene la respuesta que hace el servidor y dispone de una serie de propiedades con los valores de esa respuesta.

fetch('https://httpbin.org/ip')
    .then(function(response) {
        console.log('response.body =', response.body);
        console.log('response.bodyUsed =', response.bodyUsed);
        console.log('response.headers =', response.headers);
        console.log('response.ok =', response.ok);
        console.log('response.status =', response.status);
        console.log('response.statusText =', response.statusText);
        console.log('response.type =', response.type);
        console.log('response.url =', response.url);
        return response.json();
    })
    .then(function(data) {
        console.log('data = ', data);
    })
    .catch(function(err) {
        console.error(err);
    });

ejecutar…

El contenido del body no está disponible directamente en este objeto Response y tenemos que llamar uno de los métodos disponibles para que nos devuelva una promesa donde recibiremos el valor enviado por el servidor. Los métodos disponibles son:

  • response.text() para que nos devuelve el contenido en formato texto
  • response.json() para que lo devuelva como objeto Javascript
  • response.arrayBuffer() para obtenerlo como ArrayBuffer
  • response.blob() como valor que podemos manejar con URL.createObjectURL()
  • response.formData() para obtenerlos como FormData

Una característica que tenemos que tener en cuenta es que sólo podemos hacer una obtención de body, tras la cual ya no podemos volver solicitar otra conversión. Para resolver esta situación el objeto Response tiene el método clone() que nos permite duplicar el objeto y hacer múltiples gestiones de body.

Request

Una forma alternativa de configurar el comportamiento de fetch() es crear un objeto Request y pasar este objeto como parámetro a fetch(). El constructor de Request recibe dos parámetros: la URL y el objeto con las opciones.

var request = new Request('https://httpbin.org/get', {
        method: 'GET',
        mode: 'cors',
        credentials: 'omit',
        cache: 'only-if-cached',
        referrerPolicy: 'no-referrer'
    });
console.log('request =', request);
fetch(request)
    .then(function(response) {
        console.log('response =', response);
        return response.text();
    })
    .then(function(data) {
        console.log('data = ', data);
    })
    .catch(function(err) {
        console.error(err);
    });

ejecutar…

Este objeto Response puede ser de utilidad si tenemos que hacer varias llamadas con los mismos valores, ya que podemos reutilizarlo tantas veces como nos haga falta. Acepta todas las configuraciones que vimos en fetch().

Si en un service worker capturamos el evento fetch recibiremos un objeto event.request igual al que hemos utilizado al realizar la llamada. Para acceder al body de este Request debemos utilizar las mismas llamadas que hicimos anteriormente con Response. Realmente Response y Request implementan la interfaz Body que dispone de los diferentes métodos para el acceso al contenido del body.

Headers

Aunque podemos incluir las cabeceras por medio del objeto Request o como parte del objeto que se pasa como segundo parámetro a fetch(), tenemos a nuestra disposición el objeto Headers que nos ayuda a gestionar las cabeceras de una forma más precisa.

var headers = new Headers();
headers.append('a', '1');
headers.append('b', '2');
var request = new Request('https://httpbin.org/get', {
        headers: headers
    });
console.log('request =', request);
for (var k of request.headers.keys()) {
    console.log('request.headers.get("' + k + '") =', request.headers.get(k));
}
fetch(request)
    .then(function(response) {
        console.log('response =', response);
        for (var k of response.headers.keys()) {
            console.log('response.headers.get("' + k + '") =', response.headers.get(k));
        }
        return response.text();
    })
    .then(function(data) {
        console.log('data = ', data);
    })
    .catch(function(err) {
        console.error(err);
    });

ejecutar…

El objeto Headers se puede asignar a Request, pero también está presente en Response con las cabeceras que nos devuelve el servidor. Si queremos consultar las cabeceras devueltas tenemos que utilizar los métodos que ofrece Headers:

  • Headers.append(key, value): añade un valor a una cabecerá ya existe o crea una nueva cabecera si no existe.
  • Headers.delete(key): borra una cabecera.
  • Headers.entries(): retorna un iterador con todas las parejas clave/valor.
  • Headers.get(key): devuelve el primer valor de una cabecera.
  • Headers.getAll(key): devuelve una matriz con todos los valores de una cabecera.
  • Headers.has(key): comprueba si una cabecera existe.
  • Headers.keys(): devuelve un interador con todas las claves.
  • Headers.set(key, value): añade una cabecera nueva.
  • Headers.values(): devuelve un iterador con todos los valores.

Nota: el paquete node-fetch no ofrece la interfaz completa para Headers, sólo las append(), set(), get(), getAll() y has(). Están trabajando en una próxima versión que incluya la interfaz completa.

FormData

API fetch se apoya en otros API estandarizados como el API FormData, para poder construir de una forma más completa el body con el contenido de un formulario. Este API, también definido por WHATWG (https://xhr.spec.whatwg.org/#interface-formdata), está soportado por IE 10, Edge, Chrome 31, Firefox 12, Safari 5, Opera 18. Para Node podemos utilizar el paquete form-data https://www.npmjs.com/package/form-data, que aunque no da soporte completo, ofrece una aproximación bastante completa.

Este API se puede utilizar de dos formas, una es pasando un objeto form del DOM al constructor de FormData, por ejemplo: new FormData(document.getElementById('myForm'), la otra creando un objeto con new FormData() e ir incluyendo los valores uno a uno por medio de los métodos append() o set(). Veamos un ejemplo:

var dataSend = new FormData();
dataSend.append('a', '1');
dataSend.append('b', '2');
var request = new Request('https://httpbin.org/put', {
    method: 'PUT',
    body:      dataSend
});
console.log('request =', request);
for (var k of dataSend.keys()) {
    console.log('dataSend.get("' + k + '") =', dataSend.get(k));
}
fetch(request)
    .then(function(response) {
        console.log('response =', response);
        return response.text();
    })
    .then(function(data) {
        console.log('data = ', data);
    })
    .catch(function(err) {
        console.error(err);
    });

ejecutar…

Como se puede observar, no ha sido necesario incluir una cabecera Content-Type, ya que se añade directamente al asociar body a un objeto FormData.

Conclusiones

Aunque puede parecer que es un API muy completo, lo cierto es que le faltan algunas cosas importantes, como la capacidad de cancelar una petición ya solicitada o poder controlar el progreso de una solicitud larga, por ejemplo, al subir un fichero.

No obstante, es un interesante avance, ofreciendo un API basado en promesas, permitiendo configurar con bastante precisión la llamada, pudiendo ser utilizada en el navegador tanto en window como en worker, permitiendo la programación isomórfica en cliente y servidor, y disponiendo de polyfill para versiones de los navegadores que no disponen de soporte nativo.

Conocer y dominar este API va a ser imprescindible, por lo que esperamos que esta breve introducción haya sido de utilidad. Si quieres más detalle, puedes consultar:

Novedades

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

En esta charla, organizada por MadridJS, Pablo Almunia nos muestra cómo la mayoría de nosotros cuando oímos hablar por primera vez de HTTP2 nos ilusionamos con las posibilidades que presumiblemente se abrían para el desarrollo de soluciones web avanzadas y cómo muchos nos sentimos defraudados con lo que realmente se podía implementar.

En esta charla podemos ver cómo funciona el HTTP2, que debemos tener en cuenta en el servidor para hace uso de este protocolo y, sobre todo, cómo podemos enviar información desde el servidor al cliente de forma efectiva y fácil. Veremos con detenimiento cómo por medio de los Server-Sent Events (SSE) podemos recibir en el cliente datos enviados desde el servidor sin utilizar websocket, simplificando enormemente la construcción de aplicaciones con comunicación bidireccional.

Observables en Javascript con Proxies

Observables en Javascript con Proxies

En esta charla, organizada por MadridJS, Pablo Almunia nos habla de la observación reactiva de objetos en Javascript por medio de Proxy. Se describe paso a paso cómo funcionan los Proxies y en que casos pueden ser nuestro mejor aliado. Veremos que no hay que tenerles miedo, son bastante sencillos de utilizar, y nos ofrecen una gran abanico de posibilidades.

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

En esta charla de Madrid JS, Rafael Ventura nos describe las funcionalidades clave de NextJS, nos muestra en vivo cómo desarrollar una completa aplicación JAMStack con Server Side Rendering (SSR) y Static Site Generation (SSG) y termina mostrando como publicar esta aplicación en Vercel.

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

En esta charla Rubén Aguilera nos cuenta los problemas que tienen muchas empresas a la hora de sacar productos accesibles, vistosos y usables en el Time To Market que requiere Negocio y cómo podemos minimizar este tiempo gracias al DevUI con StencilJS para adecuar una aplicación de Angular a las exigencias del mercado en tiempo record.

breves

Descrubir algunas características de console

En el día a día nos encontramos muy a menudo utilizando console. Es una navaja multiusos que nos facilita la vida a la hora de depurar nuestro código. La mayoría de nosotros ha utilizado console.log(), pero tiene otras muchas funcionalidades.

Matrices dispersas o sparse arrays en JS

Una característica que puede producir algunos problemas, si no lo tenemos en cuenta, es la posibilidad de tener matrices con huecos, es decir, con algunos de sus elementos sin definir. Es lo que se suele denominar una matriz dispersa o sparse array. Veamos cómo trabajar con esta características de las matrices.

Operadores de bits usados con asiduidad

Cada día más a menudo podemos encontrar operadores binarios utilizados como formas abreviadas de algunas operaciones que de otra forma sería algo menos compactas y, quizás, más comprensibles. Veamos algunos casos en detalle.