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

Comparación estricta y no estricta en Javascript

Aunque pueda parecer que comparar dos valores es algo sencillo y común, lo cierto es que comparar en Javascript conlleva algunas dificultades que debemos conocer, en especial la diferencia entre la comparación estricta y no estricta, es decir, la diferencia entre == (con sólo dos signos de igualdad) y === (con sólo tres signos de igualdad).

Sistema de pruebas minimalista en Javascript

En todos los navegadores modernos y en Node disponemos de console.assert() para comprobar el resultado de nuestras funciones y construir un sencillo conjunto de pruebas sin necesidad de instalar absolutamente nada. Es un sistema minimalista, sin dependencias y que funciona muy bien. Os animamos a conocer un poco más de esta pequeña herramienta.

Esquema de módulo Javascript para navegador y Node

Una de los requisitos cada día más común es que nuestro código debe poder funcionar sin problemas tanto en el navegador como en el servidor con Node. Esto puede parecer en principio algo sencillo, pero debemos tener en cuenta algunas características de cada uno de los entornos para construir un módulo que pueda funcionar correctamente en ambos contextos.

Progressive Web Apps – Jad Joubran

The web is becoming more and more powerful everyday, especially with the launch of Progressive Web Apps. PWAs are experiences that combine the best of the web and the best of apps. You can now build web apps that run from your home screen, receive push notifications & work offline.

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.

Algunos 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.

Cómo diferenciar arrow function de function

En un reciente artículo Javier Vélez Reyes hace patente las principales diferencias entre las funciones tradicionales y las funciones flecha, ya que ambos modelos no son equivalentes e intercambiables. Veamos cómo es posible saber si una función ha sido construida por medio de la instrucción function o como una arrow function.