Pregunta 1¿Qué devuelve invoices.filter((invoice) => invoice.id === "B-302")?
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
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étodo | Devuelve | Ojo con |
|---|---|---|
| map | Un array nuevo (misma cantidad) | El array original no cambia |
| filter | Array nuevo (solo los que cumplen) | Un array vacío si ninguno cumple |
| find / findIndex | Un elemento / un índice | undefined / -1 si no lo encuentra |
| some / every | Un booleano | false / true con un array vacío |
| reduce | Lo que devuelve el último callback | Array vacío: necesita valor inicial |
| sort / toSorted | Un array ordenado | sort 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')
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
- Llama a
later("photo-17.jpg", (fileName) => { … })
- El primer resultado llega a
fileName - Aquí se llama al segundo
later
- El segundo resultado llega a
thumbnail - La salida va en la función más interna
- Empieza con
delay(100, "photo-17.jpg")
- Recibe
fileNamey devuelve el siguientedelay
- Recibe
thumbnaily lo muestra
const fileName = await delay(…)const thumbnail = await delay(…)- Escribes
console.log(…)en la línea siguiente
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.
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)
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 escribes | Qué hace | Ojo con |
|---|---|---|
| try / catch | Captura excepciones del bloque | Un catch vacío oculta los fallos |
| finally | Se ejecuta al final, siempre | Pon aquí la limpieza |
| throw new Error | Lanza una excepción ahí mismo | new Error solo no detiene nada |
| class extends Error | Tipo distinguible con instanceof | Tus tipos, antes que Error |
| cause de Error | Adjunta la excepción original | Se lee con error.cause |
| .catch de una Promise | Se llama si pasa a rejected | Va al final de los then |
Verificación de conocimientos
Responde cada pregunta una a una.
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?