Resumen de sintaxis — Métodos de arrays, código asíncrono y excepciones de un vistazo

Tablas de referencia rápida: métodos de arrays según lo que devuelven, callbacks frente a then y await, y adónde llega cada tipo de fallo.

Sumar una lista de facturas, esperar a que termine una subida de archivos, capturar un fallo y mostrar un mensaje: cada una de estas tareas se puede escribir de varias formas. Si eliges la equivocada, puedes recibir undefined donde esperabas un array, o un fallo que no llega a ningún catch.

En este artículo verás cuándo usar cada método de arrays, qué forma de esperar el trabajo asíncrono elegir y cómo capturar las excepciones.

Elegir según el resultado que quieres — lo que devuelven los métodos de arrays

Supón que de una lista de facturas quieres sacar tres cosas: cuántas están pagadas, la primera sin pagar y el importe total. Todos los métodos de arrays se usan de la misma forma, pasándoles un callback, así que solo con mirar la llamada no sabes si vas a recibir un array o un único valor.

Aquí los métodos se agrupan según la forma de su valor de retorno: un array nuevo, uno de los elementos que estaban en el array, o un valor calculado a partir del array, como un booleano o un total. Cuando conoces la forma, también sabes si puedes llamar a join sobre el resultado o leer de él una propiedad como .id.

const invoices = [
  { id: "B-301", amount: 4800, paid: true },
  { id: "B-302", amount: 1200, paid: false },
  { id: "B-303", amount: 3600, paid: true },
  { id: "B-304", amount: 2500, paid: false },
];

// Devuelve un array: map conserva la cantidad, filter se queda solo con las que cumplen
console.log(invoices.map((invoice) => invoice.id).join(", "));   // B-301, B-302, B-303, B-304
console.log(invoices.filter((invoice) => invoice.paid).length);  // 2

// Devuelve el elemento en sí: el primero que cumple
console.log(invoices.find((invoice) => !invoice.paid).id);  // B-302

// Devuelve un valor calculado a partir del array: some da un booleano, reduce da el valor acumulado
console.log(invoices.some((invoice) => invoice.amount >= 4000));              // true
console.log(invoices.reduce((total, invoice) => total + invoice.amount, 0));  // 12100
El mismo array, distintas formas de retorno
invoices4 facturas.filtercomprueba paid.findcomprueba !paid.reducesuma amountArray de 2B-301 y B-303Un elementola factura B-302Un número12100Luego.map o .joinLuego.id o .amountDirecto aconsole.log
El resultado es un array, un elemento o un número, y eso cambia lo que puedes escribir después. Si sabes qué forma de resultado quieres, ya te quedan pocos métodos entre los que elegir.

Si usas filter cuando solo quieres un elemento, recibes un array con un elemento dentro, así que tienes que agregar [0] antes de leer .id. La tabla de abajo reúne los métodos de arrays que reciben un callback vistos en este curso, agrupados según lo que devuelven.

MétodoDevuelveOjo con
mapUn array nuevo (misma cantidad)El array original no cambia
filterArray nuevo (solo los que cumplen)Un array vacío si ninguno cumple
find / findIndexUn elemento / un índiceundefined / -1 si no lo encuentra
some / everyUn booleanofalse / true con un array vacío
reduceLo que devuelve el último callbackArray vacío: necesita valor inicial
sort / toSortedUn array ordenadosort cambia también el original

Cuándo puedes seguir encadenando — cadenas de métodos y forEach

Supón que quieres el total solo de las facturas sin pagar. Si guardas el resultado de filter en una variable, llamas a map sobre esa variable y después llamas a reduce, terminas poniéndoles nombre a arrays intermedios que no vuelves a usar. Y si intentas escribirlo todo en una sola expresión sin saber qué métodos pueden ir detrás de cuáles, te encontrarás con un TypeError justo donde se unen dos de ellos.

En el encadenamiento (llamar al siguiente método sobre el valor de retorno del anterior), cada llamada como .filter(...) cuenta como un paso, y cada paso se llama sobre lo que devolvió el paso anterior. Solo puedes seguir llamando a métodos de arrays cuando el paso anterior devolvió un array.

// invoices son las mismas 4 facturas de la sección anterior

// Mientras se devuelva un array, puedes seguir agregando métodos de arrays
const unpaidTotal = invoices
  .filter((invoice) => !invoice.paid)             // Array: las 2 facturas B-302 y B-304
  .map((invoice) => invoice.amount)               // Array: [1200, 2500]
  .reduce((total, amount) => total + amount, 0);  // Número: 3700
console.log(unpaidTotal);                         // 3700

// toSorted también devuelve un array, así que puedes seguir con map y join
const idsByAmount = invoices.toSorted((a, b) => b.amount - a.amount).map((invoice) => invoice.id);
console.log(idsByAmount.join(", "));  // B-301, B-303, B-304, B-302

// forEach devuelve undefined, así que lo que encadenes después falla
invoices.forEach((invoice) => console.log(invoice.id)).map((invoice) => invoice.id);
// Después de mostrar de B-301 a B-304
// TypeError: Cannot read properties of undefined (reading 'map')
Solo puedes encadenar mientras se devuelva un array
invoicesarray de 4.filter(...)array de 2.map(...)[1200, 2500].reduce(...)3700invoicesarray de 4.forEach(...)undefined.map(...)sobre undefinedSe detiene conun TypeError
En la fila de arriba, cada paso devuelve un array hasta que reduce devuelve un número. forEach devuelve undefined, así que la cadena se rompe en el paso siguiente.

Pon al final de la cadena cualquier paso que no devuelva un array. Después de find puedes leer una propiedad como .id, pero no puedes llamar a map, y el número que devuelve reduce simplemente se guarda en una variable.

Llamar a map después de find da otro mensaje

Si encadenas .map(...) al elemento que devolvió find o al número que devolvió reduce, se detiene con un TypeError como invoices.find(...).map is not a function. El mensaje es distinto del que aparece después de forEach, pero en los dos casos el paso justo antes de .map no devolvió un array.

Comparar las formas de esperar — callbacks / then / await

Supón que subes una foto y luego creas una miniatura de ella. Las dos cosas tardan un tiempo en dar su resultado, así que las funciones no pueden entregarlo como valor de retorno, y el segundo paso tiene que esperar a que llegue el resultado del primero.

En el código de abajo, ① usa el estilo de callbacks (a la función que inicia el trabajo le pasas otra función, a la que llama cuando termina; no se devuelve ninguna Promise), que es también como funciona setTimeout. ② le pasa una función al then de la Promise que recibe, y ③ usa await dentro de una función async.

// ① Estilo de callbacks: pasas una función para que se llame al terminar, y escribes el siguiente paso dentro de ella
// later llama a callback con value después de 100ms (no devuelve una Promise)
function later(value, callback) {
  setTimeout(() => callback(value), 100);
}
later("photo-17.jpg", (fileName) => {
  later(`la miniatura de ${fileName}`, (thumbnail) => {
    console.log(`① Se creó ${thumbnail}`);  // ① Se creó la miniatura de photo-17.jpg
  });
});

// ② then: conecta el siguiente paso con .then (delay es igual que en el artículo de Promise)
delay(100, "photo-17.jpg")
  .then((fileName) => delay(100, `la miniatura de ${fileName}`))
  .then((thumbnail) => console.log(`② Se creó ${thumbnail}`));  // ② Se creó la miniatura de photo-17.jpg

// ③ await: escribe el siguiente paso en la línea de abajo y recibe el resultado en una variable (se asume que corre dentro de una función async, como en el artículo anterior)
const fileName = await delay(100, "photo-17.jpg");
const thumbnail = await delay(100, `la miniatura de ${fileName}`);
console.log(`③ Se creó ${thumbnail}`);  // ③ Se creó la miniatura de photo-17.jpg
Qué tan anidado queda cada paso siguiente
① Estilo de callbacks — pasas una función a later
  • Llama a later("photo-17.jpg", (fileName) => { … })
Dentro de (fileName) => { … }
  • El primer resultado llega a fileName
  • Aquí se llama al segundo later
Dentro de (thumbnail) => { … }
  • El segundo resultado llega a thumbnail
  • La salida va en la función más interna
② then — pasas una función a .then
  • Empieza con delay(100, "photo-17.jpg")
Dentro de la función que se pasa al primer .then
  • Recibe fileName y devuelve el siguiente delay
Dentro de la función que se pasa al segundo .then
  • Recibe thumbnail y lo muestra
③ await — recibes el resultado en una variable
  • const fileName = await delay(…)
  • const thumbnail = await delay(…)
  • Escribes console.log(…) en la línea siguiente
Solo con el estilo de callbacks de ① la función siguiente va dentro de la función que recibe el resultado. El await de ③ no crea funciones: los pasos son simplemente líneas de arriba abajo.

La función del segundo then solo recibe thumbnail, así que para usar también fileName ahí tendrías que copiarlo en una variable de fuera. Con await, fileName queda en una variable que cualquier línea de abajo puede leer. La tabla de abajo pone los tres estilos lado a lado.

EstiloDónde se recibe el resultadoCuando agregas más pasos
Estilo de callbacksArgumento de la función pasadaCada paso añade un nivel
Pasar una función a thenArgumento de la función de thenTodo queda al mismo nivel
Esperar con awaitLa variable de la izquierdaUn paso por línea

Comprobar adónde llega un fallo — try / catch y .catch

Supón que una función que aparta stock falla cuando la cantidad pedida supera lo que hay en stock. Si la función no devuelve una Promise, puedes capturar el fallo envolviendo en try / catch la línea que la llama. Pero el fallo de una función que devuelve una Promise puede llegar o no al bloque catch, según cómo escribas la llamada.

El bloque catch que va después de try recibe las excepciones que se lanzan en las líneas que envuelve. El .catch de una Promise llama a la función que le pasaste cuando esa Promise pasa a rejected.

function checkStock(count) {
  if (count > 3) {
    throw new Error(`No hay stock suficiente: ${count} unidades`);
  }
}
async function reserveStock(count) {
  await delay(100);   // delay es igual que en el artículo de Promise
  checkStock(count);  // Un throw aquí deja en rejected la Promise que devuelve esta función
}

// ① Una función que no devuelve una Promise: envuelve en try la línea que la llama
try { checkStock(5); } catch (error) { console.log(`① ${error.message}`); }
// ② El estilo then: pasa una función a .catch para recibirlo
reserveStock(5).catch((error) => console.log(`② ${error.message}`));
// ③ El estilo await: envuelve en try la línea con await
try { await reserveStock(5); } catch (error) { console.log(`③ ${error.message}`); }

// ① No hay stock suficiente: 5 unidades
// ② No hay stock suficiente: 5 unidades (a los 100ms)
// ③ No hay stock suficiente: 5 unidades (a los 100ms)
Tres lugares que reciben el mismo fallo
checkStock lanzaun error con másde 3 unidades① checkStock(5)llamada directa② reserveStock(5).catch(...)③ awaitreserveStock(5)La línea que llamalanza un errorLa Promisedevuelta quedarejectedLa línea con awaitlanza un errorBloque catchdespués de tryArgumento de lafunción de .catchBloque catchdespués de try
En ②, la línea que llama no lanza ninguna excepción: lo que pasa es que la Promise devuelta queda rejected. Un bloque catch solo recibe ① y ③, donde la propia línea lanza la excepción.

Tanto si lo recibes con .catch como con un bloque catch, llega el mismo Error, así que error.message y las comprobaciones con instanceof funcionan igual en los dos casos. La tabla de abajo resume las formas de manejar excepciones vistas en este curso.

Lo que escribesQué haceOjo con
try / catchCaptura excepciones del bloqueUn catch vacío oculta los fallos
finallySe ejecuta al final, siemprePon aquí la limpieza
throw new ErrorLanza una excepción ahí mismonew Error solo no detiene nada
class extends ErrorTipo distinguible con instanceofTus tipos, antes que Error
cause de ErrorAdjunta la excepción originalSe lee con error.cause
.catch de una PromiseSe llama si pasa a rejectedVa al final de los then
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Qué devuelve invoices.filter((invoice) => invoice.id === "B-302")?

Pregunta 2En invoices.filter(...).map(...).reduce(...), ¿qué array acumula reduce?

Pregunta 3Si encadenas tres pasos asíncronos con el estilo de callbacks, ¿dónde escribes el tercer paso?