AJAX con Fetch API

fetch

AJAX con Fetch API

Fetch API es una forma alternativa de realizar peticiones AJAX al uso del ya clásico objeto XMLHttpRequest (XHR). El uso de objetos XMLHttpRequest no es muy amigable y por ello en su lugar ha sido muy popular el uso de los métodos ajax(), post(), get(), load(), etc. que ofrece jQuery para realizar peticiones AJAX.

Fetch API se basa en el uso de promesas JavaScript, por lo que resulta necesario familiarizarse primero con ellas antes de utilizar este API. El uso de promesas permite un código más claro, limpio y sencillo, que mediante el uso del objeto XMLHttpRequest. Sin embargo, Fetch API tiene ciertas limitaciones que no tiene el objeto XMLHttpRequest, como puede ser la imposibilidad de monitorizar el progreso de la petición AJAX (útil para cuando transferimos un archivo grande al servidor, por ejemplo), o la imposibilidad de abortar/cancelar una petición AJAX.

¿Cómo hacer AJAX con XMLHttpRequest?

Antes de ver cómo funciona Fetch, es interesante ver cómo es el código para realizar una sencilla petición AJAX de forma clásica, sin uso de librerías como jQuery, mediante el uso de XMLHttpRequest.

En los siguientes ejemplos, vamos a usar la siguiente función que nos va a permitir incrustar un script al final del documento, de forma que dicho script se ejecutará inmediatamente:

function loadScr(code) {
  // añade un elemento script al final del documento
  // con el código fuente indicado por parámetro
  var scr = document.createElement("script");
  scr.innerHTML = code;
  document.body.append(scr);
}

Veamos ahora un sencillo ejemplo de cómo cargar un script por código mediante una petición AJAX asíncrona, usando el objeto XMLHttpRequest:

// creamos la petición AJAX
var req = new XMLHttpRequest();
// callback que se ejecutará si la petición se resuelve correctamente
req.onload = function() {
  // añadimos script al documento
  loadScr(this.responseText);
};
// callback que se ejecutará si la petición NO se logra resolver correctamente
req.onerror = function() {
  console.log("Error al realizar la petición AJAX.");
};
// realizamos la petición
req.open('get', './js/script.js', true);
req.send();

Este ejemplo pedirá al servidor el archivo script.js de la carpeta /js y lo añadirá como elemento <script> de forma que se ejecutará tan pronto sea cargado. Esto puede resultar útil si queremos cargar scripts bajo demanda en lugar de por defecto.

NOTA: Hay que recordar que como estamos usando AJAX, para que estos ejemplos funcionen deberemos de abrir la página HTML desde un servidor Web (usando XAMPP, por ejemplo), y no directamente desde el navegador.

Por el anterior ejemplo, puede parecer que el uso del objeto XMLHttpRequest no es muy complicado, pero el funcionamiento mediante callbacks pronto revela sus limitaciones si, por ejemplo, en lugar de un script, queremos cargar varios scripts de forma consecutiva en un orden concreto, entrando de este modo en lo que se conoce como el infierno de las callbacks (callback hell). Veamos un ejemplo de este problema;

function errorAjax() {
  console.log("Error al realizar la petición AJAX.");
}

var req1 = new XMLHttpRequest();
req1.onload = function() {
  loadScr(this.responseText);
  // pedimos un segundo script
  var req2 = new XMLHttpRequest();
  req2.onload = function() {
    loadScr(this.responseText);
    // pedimos un tercer script
    var req3 = new XMLHttpRequest();
    req3.onload = function() {
      loadScr(this.responseText);
    }
    req3.onerror = errorAjax;
    req3.open('get', './js/script3.js', true);
    req3.send();
  }
  req2.onerror = errorAjax;
  req2.open('get', './js/script2.js', true);
  req2.send();
};
req1.onerror = errorAjax;
req1.open('get', './js/script1.js', true);
req1.send();

Tal y como puede observarse este código es bastante feo, poco claro y difícil de leer.

¿Cómo hacer AJAX con la API Fetch?

Veamos de nuevo el ejemplo de cómo cargar un único script, pero esta vez usando Fetch.

fetch('./js/script.js')
  .then(function(response) {
    // si la promesa de la petición fetch se ha resuelto
    // correctamente entrará aquí.
    // sin embargo, tal vez el archivo no exista en el servidor (código 404)
    // o algún otro problema, por ello debemos de asegurarnos de que todo
    // ha ido OK (código 2XX)
    if (response.ok) {
      // response.text() devuelve otra promesa con el contenido
      // devuelto por el servidor en formato texto
      return response.text();
    } else {
      // el servidor ha respondido la petición, pero se ha producido algún
      // problema y no ha podido enviar el recurso solicitado;
      // como por ejemplo que el archivo no existe
      return Promise.reject(response.statusText);
    }
  })
  .then(function(responseText) {
    // si la promesa con el texto del script se ha resuelto
    // correctamente entrará aquí
    loadScr(responseText);
  })
  .catch(function(error) {
    // ha habido algún error al resolver alguna de las promesas
    console.log("Error al realizar la petición AJAX: " + error);
  }
);

La función fetch() devuelve una promesa. Si esa promesa se resuelve correctamente (el primer método then), es que hemos logrado pedir el recurso deseado al servidor. Pero existe la posibilidad de que el servidor no pueda enviarnos el recurso por algún motivo, como por ejemplo, que el recurso solicitado no exista. Es por ello que debemos de comprobar el valor de la propiedad ok, para saber si hemos obtenido realmente el recurso solicitado. Esto es diferente a la forma en la que trabajan los métodos AJAX de jQuery, ya que en jQuery si el recurso no existe, se considera un error.

Tanto si hemos obtenido realmente el recurso como si no, debemos de devolver una segunda promesa. Con return response.text() estamos devolviendo una promesa con la información devuelta por el servidor en formato texto. Mientras que con return Promise.reject() estamos devolviendo una promesa con un mensaje de error.

Si la segunda promesa se resuelve correctamente, se ejecutará el segundo método then.

Obviamente, la promesa que hemos devuelto con Promise.reject() nunca se resuelve correctamente por lo que siempre se ejecutará el catch. Mientras que en el caso de response.text(), sería bastante raro que esa promesa no resolviera correctamente, por lo que se ejecutará el segundo método then, y ahí ya tendremos acceso al texto que ha devuelto el servidor con el contenido del script que hemos solicitado. De todos modos, en caso de fallo también se ejecutará el método catch.

Veamos ahora como sería el ejemplo de la carga secuencial de 3 scripts con fetch:

function ajaxResponse(response) {
  if (response.ok) return response.text();
    else return Promise.reject(response.statusText);
}

fetch('./js/script1.js')
.then(ajaxResponse)
.then(function(responseText) {
  loadScr(responseText);
  // pedimos segundo script
  return fetch('./js/script2.js');
})
.then(ajaxResponse)
.then(function(responseText) {
  loadScr(responseText);
  // pedimos tercer script
  return fetch('./js/script3.js');
})
.then(ajaxResponse)
.then(function(responseText) {
  loadScr(responseText);
})
.catch(function(error) {
  // ha habido algún error al resolver alguna de las promesas
  console.log("Error al realizar la petición AJAX: " + error);
});

Bastante mejor, y eso que tenemos que lidiar con un par de promesas por cada script que pedimos.

Sin embargo, sigue siendo un código algo enrevesado para algo tan repetitivo. Afortunadamente, el uso de promesas se ha simplificado recientemente gracias a Async/Await.

Vamos a volver a escribir el ejemplo pero esta vez usando Async/Await. Además, esta vez vamos a colocar todos los scripts que queremos cargar en un array de forma que cuando se termine de cargar un script se pida el siguiente. Esto nos va a permitir añadir más scripts de forma mucho más sencilla que en los ejemplos anteriores.

(async () => {
  try {
    var scripts = ["script1.js", "script2.js", "script3.js"];
    for (scr of scripts) {
      var response = await fetch('./js/' + scr);
      if (response.ok) {
        var responseText = await response.text();
        loadScr(responseText);
      } else {
        throw new Error(response.statusText);
      }
    }
  } catch (err) {
    console.log("Error al realizar la petición AJAX: " + err.message);
  }
})();

Mucho más claro, sencillo y versátil que el ejemplo usando el objeto XMLHttpRequest, ¿no?

Lo bueno de Aync/Await es que podemos escribir un código similar al que escribiríamos si las operaciones no fueran asíncronas, y por lo tanto más lineal y sencillo de seguir.

Conclusiones

Hemos visto un poco como de diferentes pueden ser el objeto XMLHttpRequest y la API Fetch. Obviamente, ambos son más complejos y tienen mayor funcionalidad de la que hemos visto.

En un próximo artículo profundizaremos un poco en este API y veremos como enviar y recibir datos, como enviar la información de un formulario con Fetch, y algunas cosillas más.

Escribe un comentario