Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:programacion_avanzada_javascript:peticiones_asincronas_api_fetch

¡Esta es una revisión vieja del documento!


Peticiones asíncronas con la API Fetch

Módulo perteneciente al curso Programación avanzada con JavaScript y ECMAScript.

La API de fetch como alternativa a XHR

Con la aparición de las Promises era hora de que fueran apareciendo nuevas clases y funciones, en definitiva nuevas APIs nativas, que las aprovecharan.

Una de esas APIs es la API de fetch que nos permite tener un sustituto más moderno para XMLHTTPRequest. Así, en lugar de modificar XMLHTTPRequest para añadirle soporte de promises se ha optado por crear una nueva API, más adaptada a las necesidades actuales.

Como se puede ver, dicha API está disponible en cualquier navegador moderno: cualquier versión reciente de Firefox, Chrome, Edge o Safari, así como de los principales navegadores móviles la soporta.

Soporte de fetch en navegadores

La única excepción, como casi siempre, es Internet Explorer, aunque existen polyfills (el de Github o el de develop.it) para intentar suplir esta carencia.

¿Y qué nos permite hacer esta API? Pues, resumiendo, lo mismo que nos permite XMLHTTPRequest, es decir llamadas HTTP en segundo plano (habilitando así escenarios AJAX), pero de una forma más moderna y sencilla.

Vamos a verlo.

Consideraciones de seguridad previas al uso de fetch

Veamos un ejemplo de uso de la API de fetch para realizar una llamada HTTP en segundo plano. A diferencia de XMLHTTPRequest que se basa en eventos, la API fetch se basa en promises (repasa el módulo dedicado a ellas si tienes dudas).

Para realizar una llamada HTTP asíncrona se usa el método fetch definido en el objeto window (es decir, en el contexto global) del navegador. Eso significa que puedes usar directamente dicha función sin más. De todos modos si quieres comprobar si un navegador soporta fetch verifica si el resultado de fetch in window vale true.

Nota: si usas Node.js, el contexto global de este entorno no implementa este método. Para poder utilizarlo debes instalar algún polyfill específico para Node.js, por ejemplo, a través del paquete NPM node-fetch.

Realizar una petición HTTP

Vamos a ver cómo realizar una petición HTTP asíncronamente usando la función fetch. Para iniciar la petición basta con invocar dicha función con el URL deseado:

let pr = fetch ('https://www.campusmvp.es');

El valor de retorno de fetch es una promise que se resuelve una vez la petición ha terminado. Dado que lo habitual es querer esperar a que la petición termine para realizar alguna acción con el resultado, lo más común será usar el método then de la promise devuelta, así:

fetch ('http://www.campusmvp.es')
    .then(r => {
        console.log(r)
    }).
    catch (e => {
        console.error('Error!', e)
    })

Pruébalo en tu navegador. Ejecuta el código anterior directamente y observa el resultado.

Es más que probable que recibas un error. El error exacto depende del navegador. Por ejemplo, en Chrome el error devuelto es:

Error en Chrome

Por su parte, en Firefox el error es:

Error en Firefox

No importa tanto el error concreto sino el hecho de que debes tener claro que fetch sigue todas las convenciones de seguridad modernas. Eso implica, entre otras cosas:

  • Evitar cargar elementos no seguros desde orígenes seguros. Es decir, si nuestra página se ha cargado mediante HTTPS, solo podremos cargar recursos servidos vía HTTPS.
  • Por defecto fetch solo permite peticiones a recursos del mismo origen.

Vamos a habilitar la carga de recursos de otros orígenes, pasando el parámetro mode a cors:

fetch ('https://www.campusmvp.es',{mode: 'cors'})
    .then(r => {
        console.log(r)
    }).
    catch (e => {
        console.error('Error!', e)
    })

Si lo pruebas ahora, lo más seguro es que… ¡recibas otro error! En la siguiente figura se muestra el error usando Firefox:

Error de CORS

La razón de este error se llama CORS y es momento de hablar, brevemente, de él.

CORS

Una explicación exhaustiva de CORS está fuera del alcance de este curso. Básicamente debes saber que CORS es el acrónimo de “Cross Origin Resource Sharing” y especifica un conjunto de reglas que los navegadores deben seguir cuando carguen recursos que provengan de otro origen diferente al de la página actual.

Nota: en terminología web un origen es la ubicación desde la cual se carga un recurso; se trata básicamente de la combinación de protocolo, servidor y puerto. Así, si tenemos una imagen que está en http://mi-servidor.com/images/uno.gif, el origen de dicha imagen es http://mi-servidor.com:80 (80 es el puerto por defecto en HTTP). Una petición entre orígenes (cross-origin request) se da cuando, desde una página que está en un determinado origen (p. ej. el mencionado), accedemos a recursos que están en otro origen (como podría ser http://cdn.mi-servidor.com:80). Desde siempre ha habido etiquetas HTML que soportan llamadas entre orígenes, tales como <image> o <script>. Esas etiquetas tienen sus propias normas y CORS no les influye directamente. CORS afecta a las llamadas realizadas vía JavaScript, es decir, usando básicamente XMLHTTPRequest o bien la API fetch que estamos discutiendo.

CORS define un conjunto de cabeceras que navegador y servidor se intercambian para decidir si desde un determinado origen es posible acceder a los datos de otro origen. Si el cliente o el servidor no envía dichas cabeceras, la petición será denegada. Lo mismo que ocurre cuando el valor de dichas cabeceras no es el correcto.

En el caso de nuestro ejemplo, el servidor no envía las cabeceras CORS cuando se le pide la URL https://www.campusmvp.es. En concreto debería mandar una cabecera, llamada Access-Control-Allow-Origin (ACAO) en la cual se especifica qué orígenes son válidos para dicha respuesta. El cliente (es decir, el navegador) comprueba que el origen esté incluido dentro de esa lista y si no es el caso, deniega la petición.

Una cuestión importante es que, es el navegador quien realiza dicha comprobación una vez obtiene la respuesta del servidor. Eso significa que la petición se ha realizado, pero el cliente (navegador) no nos deja ver los resultados. De hecho, si vamos a la pestaña network de las herramientas de desarrollador vemos la petición:

Petición de fetch en Firefox

La petición se ha realizado, la respuesta se ha devuelto (es un 200) pero, al no existir la cabecera ACAO, el navegador nos da ese error.

Puede sorprendernos que una página “moderna”, la web de CampusMVP, no soporte CORS, pero es algo bastante normal. Lo que ocurre es que la URL que estamos usando es la de una página web completa, y raras veces se carga vía AJAX una página web completa. Así que hay muchos servidores que no soportan CORS en URLs que son páginas web completas. Los servidores suelen soportar CORS en aquellos recursos que están pensados para ser usados vía AJAX, básicamente APIs REST.

Peticiones preflight

En el caso de peticiones que modificasen el estado del servidor, el hecho de que CORS se resuelva en el lado del cliente, y no en el servidor, podría representar un problema potencial. Por ejemplo, una petición POST para comprar entradas para un concierto. En este escenario, usando CORS, el navegador realizaría el POST y luego con el resultado verificaría si la cabecera ACAO es correcta. Pero, por su parte, el servidor ya habría procesado la petición POST (y procesado la compra de la entrada) aunque el cliente no pueda ver el resultado de la operación.

Para evitar estos escenarios existe un concepto llamado preflight.

En el caso de peticiones que puedan modificar el estado del servidor (es decir que no se efectúen utilizando el verbo HTTP GET), el cliente realiza otra petición (la petición preflight) usando el verbo OPTIONS. En dicha petición el navegador pasa el origen y el verbo HTTP que se va a usar y espera la respuesta del servidor. Si el servidor entiende CORS, dará una respuesta con la cabecera ACAO establecida. Si el valor de la cabecera ACAO es correcta, solo entonces el cliente (el navegador) realizará la petición inicial.

Así, por ejemplo, tenemos un código que usa fetch para realizar un POST. El navegador, automáticamente antes del POST, enviará una petición OPTIONS al servidor (indicando el origen y estableciendo el valor de Access-Control-Request-Method a POST para indicar que la petición que se quiere realizar es un POST). El servidor responderá con un 200 y la cabecera ACAO al valor apropiado, y el cliente lo podrá verificar antes de hacer la llamada real.

El cliente tan solo realizará la petición POST si el origen que hace la petición está dentro de los valores permitidos por la cabecera ACAO devuelta. En caso contrario devolverá un error de CORS.

En las peticiones GET no se realiza el preflight porque se asume que no pueden modificar el estado del servidor y por lo tanto no es necesaria esa protección adicional.

informatica/programacion/cursos/programacion_avanzada_javascript/peticiones_asincronas_api_fetch.1729000443.txt.gz · Última modificación: por tempwin