ajax
HTTP
Fetch API
Piensa en un fetch como una simple promesa de JavaScript. Tú das una solicitud y recibes una respuesta. Fetch API nos brinda el método fetch()
, que nos permite acceder a esas solicitudes y respuestas, utilizando JavaScript.
Veamos cómo se ve una solicitud fetch sencilla:
1fetch('examples/example.json') 2 .then(response => { 3 // Aquí es donde pones lo que quieres hacer con la respuesta 4 }) 5 .catch(error => { 6 console.log('Oh No! There was a problem: \n', error); 7 });
¿Qué está sucediendo aquí?
.then
para su uso..catch
. Ten en cuenta que un 404 (una mala respuesta) sigue siendo una respuesta y, por lo tanto, no se considerará una respuesta incompleta. Como resultado, en el ejemplo anterior, no entraría automáticamente en el .catch
.¿Cómo comprobamos una respuesta exitosa?
Dado que la promesa solo rechazaría una solicitud si no pudo completarla, debemos verificar si una respuesta es buena y validarla para arrojar un error si no está bien.
Una respuesta genérica de un servidor se vería parecido a esto en la consola:
1[object Response] { 2 body: (...) 3 bodyUsed: false 4 headers: Headers {} 5 ok: true 6 redirected: false 7 status: 200 8 statusText: "OK" 9 type: "cors" 10 url: "https://assets.breatheco.de/apis/fake/todos/user/Gmihov" 11}
Para evaluar el estado de una respuesta puedes utilizar:
¿Cómo actualizaría el ejemplo anterior para validar las respuestas?
1fetch('examples/example.json') 2 .then(response => { 3 if (!response.ok) { 4 throw new Error(response.statusText); 5 } 6 // Aquí es donde pones lo que quieres hacer con la respuesta 7 }) 8 .catch(error => { 9 console.log('Looks like there was a problem: \n', error); 10 });
¿Ahora qué está pasando?
.then
para que se use de la manera que se especificó..catch
.¿Por qué necesitamos esto?
Para evitar que malas respuestas entren a nuestro código y rompan tu aplicación más adelante.
Necesitamos lanzar este error manualmente porque, como se explicó anteriormente, los mensajes de error recibidos dentro de una respuesta del servidor no se registran automáticamente como un error y no aparecen en el método .catch
.
El resultado será que la búsqueda no entregará nada y, sin embargo, el cliente no tendrá idea de que algo salió mal.
¿Ahora qué?
Ahora necesitamos "leer" la respuesta para acceder al cuerpo de la respuesta.
Como ya sabe, los únicos datos que pueden viajar a través de una conexión HTTP están en un formato plano de texto. Por lo tanto, necesitamos convertir el texto sin formato del cuerpo de la respuesta en un formato JavaScript significativo.
Afortunadamente, hay un método para eso: .json()
; el cual aplicaremos a la respuesta de este modo: response.json()
.
1fetch('examples/example.json') 2 .then(response => { 3 if (!response.ok) { 4 throw Error(response.statusText); 5 } 6 // Lee la respuesta como JSON 7 return response.json(); 8 }) 9 .then(responseAsJson => { 10 // Haz lo que quieras con la respuesta JSONificada 11 console.log(responseAsJson); 12 }) 13 .catch(error => { 14 console.log('Looks like there was a problem: \n', error); 15 });
¿Ahora qué está pasando?
Sencillo. Piénsalo en pasos separados.
Busca el recurso en la ruta dada. (Fetch obtiene la ruta al recurso y devuelve una promesa que se resolverá en un objeto de respuesta.)
Luego valida la respuesta. (Esto comprueba si la respuesta es válida (200s). Si no, salta al paso 5).
Lee la respuesta como JSON.
Registrar el resultado. (El resultado son los datos JSON recibidos.)
Atrapa algún error si lo hubiese.
Ahora que has visto lo básico, podemos realizar solicitudes más avanzadas.
El método de solicitud por defecto es el método "GET"; que es lo que hemos visto hasta ahora.
Aquí hay un ejemplo de un método POST que está creando un nuevo usuario:
1fetch('https://example.com/users.json', { 2 method: 'POST', 3 mode: 'cors', 4 redirect: 'follow', 5 headers: new Headers({ 6 'Content-Type': 'text/plain' 7 }) 8}) 9 .then(res => res.json()) 10 .then(response => { /* manejar la respuesta */ }) 11 .catch(error => console.error(error));
Nota: ten en cuenta que este ejemplo de fetch está haciendo un POST (enviando datos al servidor) en un formato plano de texto. En el desarrollo front-end moderno, esto es menos común. El tipo de contenido más común para nuestros métodos será el formato
application/json
, como se ve en el siguiente ejemplo:
1fetch('https://example.com/users', { 2 method: 'PUT', // or 'POST' 3 body: JSON.stringify(data), // la variable data puede ser un 'string' o un {objeto} que proviene de algún lugar más arriba en nuestra aplicación 4 headers: { 5 'Content-Type': 'application/json' 6 } 7}) 8 .then(res => { 9 if (!res.ok) throw Error(res.statusText); 10 return res.json(); 11 }) 12 .then(response => console.log('Success:', response)) 13 .catch(error => console.error(error));
☝ ¿Notaste algo nuevo arriba?
El "body" o cuerpo de nuestra solicitud es donde colocamos los datos que queremos enviar al servidor para su almacenamiento permanente, con nuestras solicitudes POST o PUT. Debido a que solo podemos enviar texto plano a través de HTTP, tenemos que convertir nuestros datos desde su formato JS original a un string. Hacemos eso con el método JSON.stringify()
.
Las solicitudes con el método GET o DELETE no requieren un cuerpo, ya que normalmente no se espera que envíen datos, sin embargo, también puedes incluir un cuerpo en esas solicitudes si es necesario.
Los headers HTTP o encabezados nos permiten realizar acciones adicionales en la solicitud y la respuesta. Puedes establecer headers de solicitud como se ve arriba.
Uno de los headers más comunes necesarios es el 'Content-Type'. Señala al destinatario de la solicitud (el servidor), cómo debe tratar los datos contenidos en el cuerpo de la solicitud. Debido a que la mayoría de las veces enviamos datos en algún formato de JavaScript que luego es stringified (transformado a string), necesitamos instruir al servidor para que convierta el string que recibe de nuevo a un formato JSON, como se ve en la línea 5 del ejemplo anterior.
Los encabezados se pueden enviar en una solicitud y recibir en una respuesta.
Por lo tanto, puedes usar los encabezados de la respuesta que recibes del servidor, para verificar el tipo de contenido de su cuerpo y asegurarte de recibir el formato correcto antes de seguir adelante en el proceso. Un ejemplo de esto sería:
1fetch('https://example.com/users') 2 .then(response => { 3 let contentType = response.headers.get("content-type"); 4 if (contentType && contentType.includes("application/json")) { 5 return response.json(); 6 } 7 throw new TypeError("Sorry, There's no JSON here!"); 8 }) 9 .then(jsonifiedResponse => { /* haz lo que quieras con la respuesta jsonificada */ }) 10 .catch(error => console.log(error));
Nota: Ten en cuenta que un nombre de encabezado HTTP no válido generará un
TypeError
. Se puede encontrar una lista de encabezados válidos aquí.
Javascript nos brinda una forma alternativa de realizar las peticiones HTTP utilizando fetch()
con Async / Await
async
hace que una función devuelva una Promesaawait
hace que una función espere una PromesaComencemos con el método GET y lo analizamos
1const getData = async () => { 2 const response = await fetch('https://example.com/users'); 3 if (response.ok) { 4 const data = await response.json(); 5 return data; 6 } else { 7 console.log('error: ', response.status, response.statusText); 8 /* Realiza el tratamiento del error que devolvió el request HTTP */ 9 return {error: {status: response.status, statusText: response.statusText}}; 10 }; 11};
Recordemos que GET es el método por default. Por ello no es obligatorio escribir el segundo parámetro del fetch()
Analicemos esta función
async
porque es una petición HTTP y su respuesta no es inmediataresponse
que esperará await
la respuesta del fetch()
. En esa constante almacenamos la respuesta de la petición.data
guardaremos los datos con formato JSON y retornamos esa respuesta.Nota: El protocolo HTTP siempre nos brindará una respuesta. Si esa respuesta es buena o no lo sabremos mediante los Códigos de estado de respuesta HTTP
Ahora que ya conocemos como funciona, veamos un ejemplo del método POST
1const createData = async () => { 2 const response = await fetch('https://example.com/users', { 3 method: 'POST', 4 body: JSON.stringify(dataToSend), // la variable dataToSend puede ser un 'string' o un {objeto} que proviene de algún lugar más arriba en nuestra aplicación 5 headers: { 6 'Content-Type': 'application/json' 7 } 8 }); 9 if (response.ok) { 10 const data = await response.json(); 11 return data; 12 } else { 13 console.log('error: ', response.status, response.statusText); 14 /* Realiza el tratamiento del error que devolvió el request HTTP */ 15 return {error: {status: response.status, statusText: response.statusText}}; 16 }; 17};
Aquí agregamos el segundo parámetro del fetch()
para agregar el método, el body y el headers.
Ejemplo de una petición con método PUT
1const updateData = async () => { 2 const response = await fetch('https://example.com/users', { 3 method: 'PUT', 4 body: JSON.stringify(dataToSend), // la variable dataToSend puede ser un 'string' o un {objeto} que proviene de algún lugar más arriba en nuestra aplicación 5 headers: { 6 'Content-Type': 'application/json' 7 } 8 }); 9 if (response.ok) { 10 const data = await response.json(); 11 return data; 12 } else { 13 console.log('error: ', response.status, response.statusText); 14 /* Realiza el tratamiento del error que devolvió el request HTTP */ 15 return {error: {status: response.status, statusText: response.statusText}}; 16 }; 17};
Ejemplo de una petición con el método DELETE
1const deleteData = async () => { 2 const response = await fetch('https://example.com/users', { 3 method: 'DELETE', 4 }); 5 if (response.ok) { 6 const data = await response.json(); 7 return data; 8 } else { 9 console.log('error: ', response.status, response.statusText); 10 /* Realiza el tratamiento del error que devolvió el request HTTP */ 11 return {error: {status: response.status, statusText: response.statusText}}; 12 }; 13};