Uso de Fetch
Experimental:Esta es unatecnología experimental
Comprueba laTabla de compabilidad de navegadores cuidadosamente antes de usarla en producción.
LaAPI Fetch proporciona una interfaz JavaScript para acceder y manipular partes del canal HTTP, tales como peticiones y respuestas. También provee un método globalfetch()
que proporciona una forma fácil y lógica de obtener recursos de forma asíncrona por la red.
Este tipo de funcionalidad se conseguía previamente haciendo uso deXMLHttpRequest
. Fetch proporciona una alternativa mejor que puede ser empleada fácilmente por otras tecnologías comoService Workers
. Fetch también aporta un único lugar lógico en el que definir otros conceptos relacionados con HTTP como CORS y extensiones para HTTP.
La especificación fetch difiere deJQuery.ajax()
en dos formas principales:
- El objeto Promise devuelto desde
fetch()
no será rechazado con un estado de error HTTP incluso si la respuesta es un error HTTP 404 o 500. En cambio, este se resolverá normalmente (con un estadook
configurado a false), y este solo sera rechazado ante un fallo de red o si algo impidió completar la solicitud. - Por defecto,
fetch
no enviará ni recibirá cookies del servidor, resultando en peticiones no autenticadas si el sitio permite mantentener una sesión de usuario (para mandar cookies,credentials de la opcióninit deberan ser configuradas). Desdeel 25 de agosto de 2017. La especificación cambió la politica por defecto de las credenciales asame-origin
. Firefox cambió desde la versión 61.0b13.
Una petición básica defetch
es realmente simple de realizar. Eche un vistazo al siguente código:
fetch('http://example.com/movies.json') .then(response => response.json()) .then(data => console.log(data));
Aquí estamos recuperando un archivo JSON a través de red e imprimiendo en la consola. El uso defetch()
más simple toma un argumento (la ruta del recurso que quieres obtener) y devuelve un objeto Promise conteniendo la respuesta, un objetoResponse
.
Esto es, por supuesto, una respuesta HTTP no el archivo JSON. Para extraer el contenido en el cuerpo del JSON desde la respuesta, usamos el métodojson()
(definido en elmixin deBody
, el cual está implementado por los objetosRequest
yResponse
).
Nota:El mixin deBody
tambien tiene metodos parecidos para extraer otros tipos de contenido del cuerpo. VeaseBody para más información.
Las peticiones de Fetch son controladas por la directiva deconnect-src
deContent Security Policy en vez de la directiva de los recursos que se han devuelto.
Suministrando opciones de petición
El métodofetch()
puede aceptar opcionalmente un segundo parámetro, un objetoinit
que permite controlar un numero de diferentes ajustes:
Veafetch()
, para ver todas las opciones disponibles y más detalles.
// Ejemplo implementando el metodo POST:async function postData(url = '', data = {}) { // Opciones por defecto estan marcadas con un * const response = await fetch(url, { method: 'POST', // *GET, POST, PUT, DELETE, etc. mode: 'cors', // no-cors, *cors, same-origin cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached credentials: 'same-origin', // include, *same-origin, omit headers: { 'Content-Type': 'application/json' // 'Content-Type': 'application/x-www-form-urlencoded', }, redirect: 'follow', // manual, *follow, error referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url body: JSON.stringify(data) // body data type must match "Content-Type" header }); return response.json(); // parses JSON response into native JavaScript objects}postData('https://example.com/answer', { answer: 42 }) .then(data => { console.log(data); // JSON data parsed by `data.json()` call });
Tenga en cuenta quemode: "no-cors"
solo permite un conjunto limitado de encabezados en la solicitud:
Accept
Accept-Language
Content-Language
Content-Type
with a value ofapplication/x-www-form-urlencoded
,multipart/form-data
, ortext/plain
Comprobando que la petición es satisfactoria
Una petición promisefetch()
será rechazada conTypeError
cuando se encuentre un error de red, aunque esto normalmente significa problemas de permisos o similares — por ejemplo, un 404 no constituye un error de red. Una forma precisa de comprobar que la peticiónfetch()
es satisfactoria pasa por comprobar si la promesa ha sido resuelta, además de comprobar que la propiedadResponse.ok
tiene el valortrue
que indica que el estado de la petición HTTP es OK (código 200-299). El código sería algo así:
fetch("flores.jpg") .then(function (response) { if (response.ok) { response.blob().then(function (miBlob) { var objectURL = URL.createObjectURL(miBlob); miImagen.src = objectURL; }); } else { console.log("Respuesta de red OK pero respuesta HTTP no OK"); } }) .catch(function (error) { console.log("Hubo un problema con la petición Fetch:" + error.message); });
Proporcionando tu propio objeto Request
En lugar de pasar la ruta al recurso que deseas solicitar a la llamada del métodofetch()
, puedes crear un objeto de petición utilizando el constructorRequest()
, y pasarlo como un argumento del métodofetch()
:
var myHeaders = new Headers();var myInit = { method: "GET", headers: myHeaders, mode: "cors", cache: "default",};var myRequest = new Request("flowers.jpg", myInit);fetch(myRequest) .then(function (response) { return response.blob(); }) .then(function (myBlob) { var objectURL = URL.createObjectURL(myBlob); myImage.src = objectURL; });
Request()
acepta exactamente los mismos parámetros que el métodofetch()
. Puedes incluso pasar un objeto de petición existente para crear una copia del mismo:
var anotherRequest = new Request(myRequest, myInit);
Esto es muy útil ya que el cuerpo de las solicitudes y respuestas son de un sólo uso. Haciendo una copia como esta te permite utilizar la petición/respuesta de nuevo, y al mismo tiempo, si lo deseas, modificar las opciones deinit
. La copia debe estar hecha antes de la lectura del <body>, y leyendo el <body> en la copia, se marcará como leido en la petición original.
Nota:Existe también un métodoclone()
que crea una copia. Este tiene una semántica ligeramente distinta al otro método de copia — el primero fallará si el cuerpo de la petición anterior ya ha sido leído (lo mismo para copiar una respuesta), mientras queclone()
no.
Enviar una petición con credenciales incluido
Para producir que los navegadores envien una petición con las credenciales incluidas, incluso para una llamada de origen cruzado, añadimoscredentials: 'include'
en el el objetoinit
que se pasa al métodofetch()
.
fetch("https://example.com", { credentials: "include",});
Si solo quieres enviar la credenciales si la URL de la petición está en el mismo origen desde donde se llamada el script, añadecredentials: 'same-origin'
.
// El script fué llamado desde el origen 'https://example.com'fetch("https://example.com", { credentials: "same-origin",});
Sin embargo para asegurarte que el navegador no incluye las credenciales en la petición, usacredentials: 'omit'
.
fetch("https://example.com", { credentials: "omit",});
Enviando datos JSON
Usafetch()
para enviar una petición POST con datos codificados en JSON .
var url = "https://example.com/profile";var data = { username: "example" };fetch(url, { method: "POST", // or 'PUT' body: JSON.stringify(data), // data can be `string` or {object}! headers: { "Content-Type": "application/json", },}) .then((res) => res.json()) .catch((error) => console.error("Error:", error)) .then((response) => console.log("Success:", response));
Enviando un archivo
Los archivos pueden ser subido mediante el HTML de un elemento input<input type="file" />
,FormData()
yfetch()
.
var formData = new FormData();var fileField = document.querySelector("input[type='file']");formData.append("username", "abc123");formData.append("avatar", fileField.files[0]);fetch("https://example.com/profile/avatar", { method: "PUT", body: formData,}) .then((response) => response.json()) .catch((error) => console.error("Error:", error)) .then((response) => console.log("Success:", response));
Cabeceras
La interfazHeaders
te permite crear tus propios objetos de headers mediante el constructorHeaders()
. Un objeto headers es un simple multi-mapa de nombres y valores:
var content = "Hello World";var myHeaders = new Headers();myHeaders.append("Content-Type", "text/plain");myHeaders.append("Content-Length", content.length.toString());myHeaders.append("X-Custom-Header", "ProcessThisImmediately");
Lo mismo se puede lograr pasando un "array de arrays" o un objeto literal al constructor:
myHeaders = new Headers({ "Content-Type": "text/plain", "Content-Length": content.length.toString(), "X-Custom-Header": "ProcessThisImmediately",});
Los contenidos pueden ser consultados o recuperados:
console.log(myHeaders.has("Content-Type")); // trueconsole.log(myHeaders.has("Set-Cookie")); // falsemyHeaders.set("Content-Type", "text/html");myHeaders.append("X-Custom-Header", "AnotherValue");console.log(myHeaders.get("Content-Length")); // 11console.log(myHeaders.getAll("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]myHeaders.delete("X-Custom-Header");console.log(myHeaders.getAll("X-Custom-Header")); // [ ]
Algunas de estas operaciones solo serán utiles enServiceWorkers
, pero estas disponen de una mejor API para manipularheaders
.
Todos los métodosde deheaders
lanzan unTypeError
si un nombre de cabecera no es un nombre de cabecera HTTP válido. Las operaciones de mutación lanzarán unTypeError
si hay un guarda inmutable (ver más abajo). Si no, fallan silenciosamente. Por ejemplo:
var myResponse = Response.error();try { myResponse.headers.set("Origin", "http://mybank.com");} catch (e) { console.log("Cannot pretend to be a bank!");}
Un buen caso de uso paraheaders
es comprobar cuando el tipo de contenido es correcto antes de que se procese:
fetch(myRequest).then(function (response) { var contentType = response.headers.get("content-type"); if (contentType && contentType.indexOf("application/json") !== -1) { return response.json().then(function (json) { // process your JSON further }); } else { console.log("Oops, we haven't got JSON!"); }});
Guarda (Guard)
Desde que las cabeceras pueden ser enviadas en peticiones y recibidas en respuestas, y tienen limitaciones sobre que información puede y debería ser mutable, los objeto headers tienen una propierdad de guarda. Este no está expuesto a la Web, pero puede afectar a que operaciones de mutación son permitidas sobre el objeto headers.
Los valores posibles de guarda (guard) son:
none
: valor por defecto.request
: Guarda para el objeto headers obtenido de la petición (Request.headers
).request-no-cors
: Guarda para un objeto headers obtenido desde una petición creada conRequest.mode
ano-cors
.response
: Guarda para una cabecera obetenida desde un respuesta (Response.headers
).immutable
: Mayormente utilizado para ServiceWorkers, produce un objeto headers de solo lectura.
Nota:No se debería añadir o establecer una petición a un objeto headersguardado con la cabeceraContent-Length
. De igual manera, insertarSet-Cookie
en la respuesta de la cabecera no esta permitido: ServiceWorkers no estan autorizados a establecer cookies a través de respuestas sintéticas.
Objetos Response
Cómo has visto anteriormente, las instancias deResponse
son devueltas cuandofetch()
es resuelto.
Las propiedades de response que usarás son:
Response.status
— Entero (por defecto con valor 200) que contiene el código de estado de las respuesta.Response.statusText
— Cadena (con valor por defecto "OK"), el cual corresponde al mensaje del estado de código HTTP.Response.ok
— Visto en uso anteriormente, es una clave para comprobar que el estado está dentro del rango 200-299 (ambos incluidos). Este devuelve un valorBoolean
, siendotrue
si lo anterior se cumple yfalse
en otro caso.
Estos pueden también creados programáticamente a través de JavaScript, pero esto solamente es realmete útil enServiceWorkers
, cuando pones un objeto response personalizado a una respuesta recibida usando un métodorespondWith()
:
var myBody = new Blob();addEventListener("fetch", function (event) { event.respondWith( new Response(myBody, { headers: { "Content-Type": "text/plain" }, }), );});
El constructorResponse()
toma dos argurmentos opcionales, un cuerpo para la respuesta y un objeto init (similar al que aceptaRequest()
).
Nota:El método estáticoerror()
simplemente devuelve un error en la respuesta. De igual manera queredirect()
devuelve una respuesta que resulta en un redirección a una URL especificada. Estos son solo relevantes tambien a ServiceWorkers.
Body
Tanto las peticiones como las respuestas pueden contener datos body. Body es una instancia de cualquiera de los siguientes tipos:
ArrayBuffer
ArrayBufferView
(Uint8Array y amigos)Blob
/File- string
URLSearchParams
FormData
El mixin deBody
define los siguientes metodos para extraer un body (implementado porRequest
andResponse
). Todas ellas devuelven una promesa que es eventualmente resuelta con el contenido actual.
Este hace uso de los datos no texttuales mucho mas facil que si fuera con XHR.
Las peticiones body pueden ser establecidas pasando el parametro body:
var form = new FormData(document.getElementById("login-form"));fetch("/login", { method: "POST", body: form,});
Tanto peticiones y respuestas (y por extensión la functionfetch()
), intentaran inteligentemente determinar el tipo de contenido. Una petición tambien establecerá automáticamente la propiedadContext-Type
de la cabecera si no es ha establecido una.
Detectar característica
Polyfill
Para utilizarfetch()
en un explorador no soportado, hay disponible unFetch Polyfill que recrea la funcionalidad para navegadores no soportados.