Pregunta 1Después de ejecutar const response = await fetch(url);, ¿qué contiene response?
fetch — obtener datos de una API
fetch y json() para obtener JSON de una URL, response.ok para detectar fallos, URL relativas y Promise.all para cargar dos archivos a la vez.
Hasta ahora, los ejercicios tenían los datos escritos directamente en el código. En un sitio real, los precios y el stock cambian, así que la página consulta la API del servidor (una URL de la que los programas obtienen datos) cada vez que se abre.
En este artículo verás fetch, que obtiene datos de una URL, y response.ok, que indica si la solicitud salió bien.
Obtener el JSON de una API — fetch y json()
Supón que quieres mostrar en una página de historial los pedidos guardados en el servidor. Los pedidos llegan como una cadena JSON, así que necesitas una forma de esperar a que lleguen y un paso que convierta esa cadena de nuevo en un array. En los ejercicios, en lugar de una API real, vas a obtener archivos JSON de la carpeta fixtures de este sitio.
fetch (una función que le pide al servidor los datos de una URL) devuelve una Promise que pasa a fulfilled con un Response, el objeto que representa la respuesta. Primero llegan el status (un número que vale 200 si todo salió bien) y los encabezados (información sobre la respuesta que se envía antes del cuerpo), y el cuerpo llega después. Por eso haces await de response.json() una segunda vez para obtenerlo.
// Obtiene el JSON de pedidos guardado en este sitio (cómo escribir la URL se ve en la siguiente sección)
const response = await fetch("fixtures/es/orders.json");
console.log(response.status); // 200
// Lee el cuerpo completo y lo convierte de JSON a un array
const orders = await response.json();
console.log(orders.length); // 3
console.log(orders[1].total); // 12600
// A partir de aquí es un array normal
const preparing = orders.filter((order) => order.status === "En preparación");
console.log(preparing.map((order) => order.id).join(", ")); // A-3002, A-3003
json() lee el cuerpo hasta el final y aplica la misma conversión que JSON.parse antes de devolver el valor, así que esperas el resultado con await. Una vez convertido, orders es un array normal y puedes usar filter y map directamente.
Si imprimes un Response, sale {}
En la consola de los ejercicios, console.log(response) muestra {}, igual que con los elementos del DOM. response.json() sin await es una Promise, así que también muestra {}. Para revisar un Response, imprime su status; para revisar el cuerpo, imprime el valor que obtienes al hacer await de json().
Escribir la URL a partir de la página — URL relativas
En la sección anterior bastó con escribir "fixtures/es/orders.json" para obtener el archivo. Una URL completa que empieza con https:// incluye el dominio (la parte que va después de https://, como runner.desktechlearn.com, que identifica el sitio), así que tendrías que cambiarla si el sitio cambiara de dominio.
Una URL relativa (una URL que se escribe a partir de la URL de la página actual) se resuelve desde la carpeta de la página si no empieza con /, y desde la raíz del dominio si empieza con /. El código de los ejercicios no se ejecuta en la página del artículo, sino en runner.html, una página sin interfaz que está en runner.desktechlearn.com.
// URL de la página donde se ejecuta este código (la consola de los ejercicios)
// https://runner.desktechlearn.com/tools/js-editor/runner.html
// Sin / al inicio: se resuelve desde js-editor, la carpeta de runner.html
const fromPage = await fetch("fixtures/es/orders.json");
console.log(fromPage.url); // url es la URL que realmente se solicitó
// https://runner.desktechlearn.com/tools/js-editor/fixtures/es/orders.json
// Con / al inicio: se resuelve desde la raíz del dominio
const fromRoot = await fetch("/tools/js-editor/fixtures/es/orders.json");
console.log(fromRoot.url === fromPage.url); // true (apuntan al mismo archivo)
// Si olvidas fixtures/es/, busca directamente dentro de js-editor
const noFolder = await fetch("orders.json");
console.log(noFolder.url);
// https://runner.desktechlearn.com/tools/js-editor/orders.json
- Las URL que empiezan con
/parten de aquí
runner.html— la página donde se ejecuta el código de los ejercicios- Las URL sin
/al inicio parten de aquí
- Contiene
orders.json,users.jsonyproducts.json
Una URL que empieza con / apunta al mismo archivo sin importar en qué carpeta esté la página. Una URL sin / sigue funcionando si mueves la página y fixtures juntas a otra carpeta. La tabla de abajo muestra desde dónde parte cada URL y si encuentra el archivo.
| URL | Parte de | ¿Encuentra el archivo? |
|---|---|---|
| fixtures/es/orders.json | js-editor | Sí |
| /tools/js-editor/fixtures/es/orders.json | Raíz del dominio | Sí (el mismo archivo) |
| orders.json | js-editor | No (no está en js-editor) |
En DevTools, la URL parte de la página del artículo
Si abres la consola de DevTools en la página del artículo y ejecutas fetch("fixtures/es/orders.json"), el punto de partida es la URL de la página del artículo, así que la solicitud nunca llega a fixtures. El servidor de la página del artículo devuelve el HTML de la página con status 200 incluso para rutas que no existen, así que puede parecer que funcionó, pero lo que recibes no es JSON.
Detectar una solicitud fallida — response.ok y status
Si escribes mal una URL o el archivo se borró, el servidor responde que no lo encontró. Aun así llega una respuesta, así que fetch pasa a fulfilled con un Response. Aunque lo envuelvas en try / catch, la ejecución no salta a catch: sigue con la siguiente línea, la que lee el cuerpo.
response.ok es true cuando status está entre 200 y 299. El 200 no es el único código de éxito (el 201 también lo es), así que usa ok para decidir si la solicitud salió bien. Lo habitual es que un servidor devuelva 404 (Not Found) cuando falta un archivo, pero el servidor de la consola de los ejercicios devuelve 403 (Forbidden).
// Una URL con orders mal escrito como order
const response = await fetch("fixtures/es/order.json");
// Aunque no encuentre el archivo, fetch pasa a fulfilled y devuelve un Response
console.log(response.ok); // false
console.log(response.status); // 404 o 403 (depende del servidor; la consola de los ejercicios devuelve 403)
// Convierte el cuerpo en array solo si ok es true
if (response.ok) {
const orders = await response.json();
console.log(`Pedidos: ${orders.length}`);
} else {
console.log("No se pudieron cargar tus pedidos"); // No se pudieron cargar tus pedidos
}
En la columna de la derecha, el cuerpo no es JSON sino un texto que describe el error (en la consola de los ejercicios, en formato XML), así que json() lanza un SyntaxError en esa línea. La columna del centro revisa ok primero y muestra un mensaje sin leer el cuerpo, así que la solicitud fallida termina en un mensaje en pantalla en lugar de un error.
Obtener dos archivos a la vez — funciones async y Promise.all
Un panel de administración que muestra lado a lado la cantidad de miembros y de pedidos necesita dos archivos JSON, y ninguno depende del otro, así que los cargas en paralelo (empezar varias tareas a la vez y luego esperar), como viste en el artículo de async / await. Pero si llamas a fetch dos veces y pasas los resultados a Promise.all, lo que obtienes son dos Response, no los cuerpos.
Si juntas todo, desde fetch hasta json(), en una función async, la Promise que devuelve al llamarla pasa a fulfilled con el valor del cuerpo. Si llamas a esa función dos veces y esperas con Promise.all, obtienes un array con los valores de los cuerpos en el mismo orden en que los pasaste.
// Junta todo desde fetch hasta json() y devuelve el valor del cuerpo (null si no lo encuentra)
async function loadJson(path) {
const response = await fetch(path);
if (!response.ok) {
return null;
}
return await response.json();
}
// Si pasas dos llamadas a fetch, obtienes Response
const responses = await Promise.all([
fetch("fixtures/es/products.json"),
fetch("fixtures/es/orders.json"),
]);
console.log(responses[0].length); // undefined (no es un array)
// Si llamas dos veces a loadJson, las dos empiezan a la vez y Promise.all junta los arrays de los cuerpos
const [products, orders] = await Promise.all([
loadJson("fixtures/es/products.json"),
loadJson("fixtures/es/orders.json"),
]);
console.log(`Productos: ${products.length} / Pedidos: ${orders.length}`); // Productos: 4 / Pedidos: 3
Basta con que una de las Promises pase a rejected para que Promise.all falle sin entregar ninguno de los otros resultados. loadJson pasa a fulfilled con null cuando ok es false, así que, aunque falte un archivo, igual recibes el otro array.
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2Con una ruta que no existe, ¿qué ocurre justo después de ejecutar const res = await fetch(url);?
Pregunta 3En código que se ejecuta en /tools/js-editor/runner.html, ¿dónde busca fetch("users.json")?