Aprende leyendo en orden

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
Cuándo llega cada parte y los dos await
fetch(url) envíala solicitudLa Promise sigueen pendingLlegan el status200 y encabezadosawait fetch(…)devuelve ResponseLlega todo elcuerpo y seconvierte de JSONawait json()devuelve 3 pedidos
El await de fetch termina en cuanto llegan los encabezados. El array solo aparece después del await de json(), que lee el cuerpo hasta el final.

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().

En una pantalla de administración de miembros, obtén del servidor la lista de miembros y muestra sus nombres. La lista está disponible en fixtures/es/users.json.

① Obtén la lista de miembros y muestra el status de la respuesta.

② Convierte el cuerpo en un array y muestra cuántos miembros hay.

③ Saca solo los nombres, únelos con «, » y muestra el resultado.

(Si se ejecuta correctamente, aparecerá una explicación.)

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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
Dónde están runner.html y fixtures
runner.desktechlearn.com — raíz del dominio (/)
  • Las URL que empiezan con / parten de aquí
Carpeta /tools/js-editor/
  • runner.html — la página donde se ejecuta el código de los ejercicios
  • Las URL sin / al inicio parten de aquí
Carpeta fixtures/es
  • Contiene orders.json, users.json y products.json
fixtures está dentro de js-editor, la misma carpeta que runner.html. Que la URL empiece o no con / decide desde dónde se busca.

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.

URLParte de¿Encuentra el archivo?
fixtures/es/orders.jsonjs-editor
/tools/js-editor/fixtures/es/orders.jsonRaíz del dominioSí (el mismo archivo)
orders.jsonjs-editorNo (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.

En el panel de administración de una tienda de artículos para el hogar, vas a sumar los precios de los productos. brokenPath contiene la URL de la lista de productos, pero le falta la carpeta fixtures/es/.

① Obtén brokenPath y muestra si el status es 200 (true / false).

② Corrige la URL para que parta de la carpeta de la página, obtenla y muestra cuántos productos hay.

③ Obtén el archivo con una URL que parta de la raíz del dominio y muestra la suma de los precios.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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
}
Tres casos al revisar ok
await fetch(url)siempre fulfilledorders.jsonstatus 200order.jsonstatus 403, etc.order.jsonstatus 403, etc.ok es truellama a json()ok es falseno llama a json()No revisa ok yllama a json()Recibe un arrayde 3 pedidosMuestra unmensaje de errorSyntaxErrorel cuerpo noes JSON
fetch pasa a fulfilled aunque el archivo no exista. Llamar a json() con un cuerpo que no es JSON lanza un SyntaxError.

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.

Vas a cargar los productos y las reseñas que se muestran en una página de producto. Las reseñas todavía no se publicaron.

① Obtén fixtures/es/products.json y muestra si salió bien (true / false).

② Obtén fixtures/es/reviews.json y muestra si salió bien.

③ Si ② falló, muestra «Todavía no hay reseñas»; si salió bien, muestra la cantidad en la forma «Reseñas: 5».

④ Si ① salió bien, convierte el cuerpo en un array y muestra el nombre del producto que tiene 0 en stock.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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
Los resultados de fetch no traen los cuerpos
Pasa 2 llamadasa fetch()Termina al llegarlos 2 ResponseObtienes2 Responseresponses[0].lengthes undefinedPasa 2 llamadasa loadJson()Cada una esperahasta json()Obtienes2 arraysproducts.lengthes 4
La fila de arriba termina en cuanto llegan los Response. Para juntar los cuerpos, pasa los resultados de una función que espera hasta json().

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.

En una pantalla de resumen de ventas, carga a la vez los miembros, los pedidos y las reseñas. El array de rutas paths ya está declarado, y las reseñas todavía no se publicaron.

① Escribe una función async loadList que devuelva el valor del cuerpo si sale bien y un array vacío si falla.

② Pasa cada ruta de paths por loadList, espéralas todas juntas y muestra una línea como «Miembros: 4 / Pedidos: 3 / Reseñas: 0».

③ Busca por userId al miembro que hizo el primer pedido y muestra su nombre.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1Después de ejecutar const response = await fetch(url);, ¿qué contiene response?

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")?