Polyfill para Fetch

  • Por
Cómo conseguir que el API de Fetch sea compatible con todos los navegadores mediante la carga condicional de un Polyfill.

En un pasado artículo aprendimos lo básico para comenzar a utilizar el nuevo API Fetch para realizar Ajax, basado en promesas de ES6. Comenzar con Fetch no es difícil y nos ayudará a organizar nuestro código, pero de nada sirve si no es compatible con todos los navegadores usados en la actualidad.

Lo cierto es que el soporte está bastante extendido, pero no se encuentra disponible en Internet Explorer (11 y anteriores) y tampoco en los Android antiguos (4.4 y anteriores). Es por ello que tenemos que disponer de una capa alternativa de compatibilidad. La solución pasa por instalar el correspondiente Polyfill, que nos servirá para suplir mediante un script la carencia de los navegadores que no soportan fetch de manera nativa.

Existen diversos polyfills de Fetch, más o menos complejos, con mayor o menor rango de utilidades y navegadores objetivo. Puedes hacer una simple búsqueda en Google, pero creo que una buena solución, que implementaremos en el presente artículo, es el polyfill de Fetch que ha creado el equipo de Github.

Para agregar el polyfill a nuestro proyecto podemos descargarnos su archivo js desde github, pero también podríamos instalarlo usando cualquiera de los gestores de dependencias más habituales.

npm install whatwg-fetch --save

o bien:

bower install fetch --save

Polyfill para las promesas ES6

Con este único polyfill no tendremos suficiente, ya que fetch funciona usando características de ES6 que tampoco están disponibles en todos los navegadores. Por ello vamos a tener que depender de un segundo Polyfill para asegurarnos la compatibilidad con las promesas.

También existen varios polyfills para agregar soporte a las promesas de ECMAScript 2015. En nuestro caso usaremos uno llamado "es6-promise": https://github.com/stefanpenner/es6-promise

Para instalarlo también podemos usar nuestro gestor de dependencias frontend, por ejemplo:

npm install --save es6-promise

O bien

bower install --save es6-promise

Carga condicional del Polyfill

Incorporar un polyfill en un proyecto es tan sencillo como colocar un script Javascript. Sin embargo, en los navegadores modernos que ofrecen soporte nativo a fetch no deberíamos cargar ese script, dado que no produciría ningún resultado útil y estaríamos agregando un peso innecesario a nuestro sitio web. Es por ello que, en general con todos los polyfill, lo adecuado es realizar una carga condicional.

La manera de descubrir si un navegador dispone o no de soporte nativo a fetch es tan sencillo como comprobar si window.fetch tiene algún valor distinto de indefinido. Si window.fetch es indefinido querrá decir que el navegador del usuario no es capaz de entender qué es fetch, en cuyo caso habría que cargar el polyfill. Si el navegador ya conoce window.fetch entonces quiere decir que tiene soporte nativo y que no hace falta cargar el polyfill.

if(! window.fetch) {
    // debo cargar el polyfill
} else {
    // tengo soporte nativo a fetch
}

El anterior código te muestra un condicional para saber si está o no disponible fetch, pero nos queda lo más importante, realizar la carga del script para los navegadores que no conocen este API de acceso Ajax. Esto lo podríamos hacer de varias formas, pero una muy sencilla es crear al vuelo una etiqueta script, con el src dirigido al archivo del Polyfill, e inyectar finalmente esa etiqueta script en la página, para que el navegador procese su código.

A continuación veremos una función que realiza la carga condicional de un script pasado por parámetro. Además esta función recibe un segundo parámetro con una función callback, que se ejecutará cuando el script ya haya sido procesado por Javascript.

Nota: La función que voy a mostrar a continuación no es mía. La he obtenido de https://philipwalton.com/articles/loading-polyfills-only-when-needed/
function cargarScript(src, hecho) {
  var js = document.createElement('script');
  js.src = src;
  js.onload = function() {
    hecho();
  };
  js.onerror = function() {
    hecho(new Error('Intento fallido de cargar el script ' + src));
  };
  document.head.appendChild(js);
}

Usando esta función, la carga condicional del Polyfill quedaría de la siguiente forma:

if(! window.fetch) {
  console.log('no tengo fetch, debo cargar los polyfills');
  cargarScript('./polyfills.js', configureAjaxCalls);
} else {
  console.log('Sí dispongo de fetch');
  configureAjaxCalls();
}

Primero se comprueba si no existe soporte a fetch. En caso que no exista se cargan los polyfill. Para simplificarnos la vida suponemos que todos los polyfill que vas a necesitar los has concatenado para formar un único script. Si el navegador detecta que le falta soporte a fetch lo más seguro es que tampoco tenga soporte a las promesas ES6, por lo que tiene sentido agrupar todos los polyfill en un único archivo y cargar todo el código con una sola solicitud al servidor.

Una vez cargado el polyfill, o comprobado que no se necesita incluir, simplemente llamamos a la función configureAjaxCalls, que es donde realmente vamos a configurar las llamadas Ajax. Lo interesante es que, gracias a la carga del Polyfill, podemos estar seguros que el navegador podrá interpretar las llamadas a fetch, incluso aunque no exista soporte nativo. Gracias a ello, el resto del código del ejercicio será exactamente igual que el que vimos en el artículo anterior. En resumen, podrás usar fetch sin tener que preocuparte de si el navegador es compatible o no con este nuevo API de acceso a Ajax.

Ejemplo completo de uso de fetch y carga condicional del polyfill

Ahora vamos a evolucionar un poco el ejercicio anterior de fetch, incorporando el tratamiento para realizar la carga del polyfill, en el caso que sea necesario. Además hemos incorporado una división para mostrar los resultados en el cuerpo de la página, en vez de mandar únicamente mensajes a la consola.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Fetch</title>
</head>
<body>
  <button id="btn">Hacer una conexión con Ajax</button>
  <p id="respuesta"></p>
 
  <script>
  
  function cargarScript(src, hecho) {
    var js = document.createElement('script');
    js.src = src;
    js.onload = function() {
      hecho();
    };
    js.onerror = function() {
      hecho(new Error('Intento fallido de cargar el script ' + src));
    };
    document.head.appendChild(js);
  }
 
  document.addEventListener('DOMContentLoaded', initialize);
 
  function initialize() {
    //compruebo si dispongo de fetch
    if(! window.fetch) {
      console.log('no tengo fetch, debo cargar los polyfills');
      cargarScript('./polyfills.js', configureAjaxCalls);
    } else {
      console.log('Sí dispongo de fetch');
      configureAjaxCalls();
    }
  }
 
  function configureAjaxCalls() {
    document.getElementById('btn').addEventListener('click', function() {
      fetch('test.txt')
        .then(ajaxPositive)
        .catch(showError);
    });
 
    function ajaxPositive(response) {
      console.log('response.ok: ', response.ok);
      if(response.ok) {
        response.text().then(showResult);
      } else {
        showError('status code: ' + response.status);
        return false;
      }
    }
 
    function showResult(txt) {
      console.log('muestro respuesta: ', txt);
      if(txt) {
        var resElement = document.getElementById('respuesta');
        resElement.textContent = txt;
        resElement.style.color = 'blue';
      }
    }
 
    function showError(err) { 
      console.log('muestor error', err);
      var resElement = document.getElementById('respuesta');
      resElement.textContent = 'Hubo un error: ' + err;
      resElement.style.color = 'red';
    }
  }
  </script>
</body>
</html>

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir