Ir al contenido
angel.library

Ciclos e iteración en JavaScript

Usa for...of para valores iterables y for...in para claves enumerables de un objeto. break termina, continue salta una iteración. Los métodos de Array expresan transformaciones; un ciclo manual ofrece más control. forEach no espera Promises.

Un ciclo repite un bloque mientras exista trabajo o se cumpla una condición. La elección depende de si conoces la cantidad de iteraciones, recorres un iterable, necesitas propiedades de un objeto o esperas valores asíncronos.

EstructuraRecorre o controlaCaso de uso
forcontador y condición explícitosíndices, rangos y pasos
whilecondición evaluada antescantidad desconocida
do...whilecondición evaluada despuésejecutar al menos una vez
for...ofvalores de un iterableArray, String, Map, Set, NodeList
for...inclaves enumerablesobjetos simples con comprobación propia
for await...ofvalores de iterable async o Promisesstreams y paginación asíncrona

Los ciclos son sentencias: no retornan una colección. Para transformar un array y obtener otro, map o filter suelen comunicar mejor el resultado.

Tiene inicialización, condición y actualización. Cualquiera puede omitirse, pero una forma demasiado libre puede ser difícil de leer.

const values = []
for (let index = 0; index < 4; index += 1) {
values.push(index * 2)
}
values // [0, 2, 4, 6]
const reverseIndexes = []
for (let index = 5; index >= 1; index -= 2) {
reverseIndexes.push(index)
}
reverseIndexes // [5, 3, 1]

Usa let para el contador. Cada iteración obtiene su binding de bloque, lo que evita el problema histórico de capturar una única variable var en callbacks.

const callbacks = []
for (let index = 0; index < 3; index += 1) {
callbacks.push(() => index)
}
callbacks.map((callback) => callback()) // [0, 1, 2]

Evalúa la condición antes de cada vuelta, por lo que puede ejecutarse cero veces. Es apropiado cuando el final depende de un estado y no de un rango conocido.

let remaining = 13
let attempts = 0
while (remaining > 1) {
remaining = Math.ceil(remaining / 2)
attempts += 1
}
remaining // 1
attempts // 4

Mantén visible qué hace que la condición llegue a ser falsa. Un while (true) necesita una salida clara con break, retorno o excepción.

Ejecuta el cuerpo antes de comprobar la condición, así que siempre corre al menos una vez.

let page = 1
const visited = []
do {
visited.push(page)
page += 1
} while (page <= 3)
visited // [1, 2, 3]

Es útil para menús, reintentos o lectura inicial, siempre que la primera operación deba ocurrir incluso si la condición empieza siendo falsa.

const totalByTag = new Map([
["javascript", 12],
["css", 8]
])
const rows = []
for (const [tag, total] of totalByTag) {
rows.push(`${tag}: ${total}`)
}
rows // ['javascript: 12', 'css: 8']

Strings, arrays, typed arrays, Map, Set, NodeList, generators y muchos objetos del navegador son iterables. Un objeto simple no lo es automáticamente.

for (const character of "A😀B") {
console.log(character)
}
// 'A'
// '😀'
// 'B'
const names = ["Ana", "Leo"]
for (const [index, name] of names.entries()) {
console.log(index, name)
}
// 0 'Ana'
// 1 'Leo'

for...in recorre nombres de propiedades enumerables, incluidas las heredadas. Para un objeto de datos, filtra con Object.hasOwn.

const settings = { theme: "dark", compact: true }
const result = []
for (const key in settings) {
if (!Object.hasOwn(settings, key)) continue
result.push([key, settings[key]])
}
result
// [['theme', 'dark'], ['compact', true]]

No uses for...in para valores de un array: entrega índices como strings y puede incluir propiedades adicionales.

const numbers = [10, 20]
numbers.extra = 30
Object.keys(numbers) // ['0', '1', 'extra']
for (const key in numbers) console.log(key)
// '0', '1', 'extra'
for (const value of numbers) console.log(value)
// 10, 20

Para objetos, Object.keys, Object.values y Object.entries suelen producir recorridos más explícitos.

break termina el ciclo actual. continue omite el resto de la vuelta y continúa con la siguiente.

const input = [3, -1, 4, 0, 8]
const positives = []
for (const number of input) {
if (number === 0) break
if (number < 0) continue
positives.push(number)
}
positives // [3, 4]

Las etiquetas permiten salir de ciclos anidados, pero suelen indicar que conviene extraer una función y usar return.

function findPosition(matrix, expected) {
for (let row = 0; row < matrix.length; row += 1) {
for (let column = 0; column < matrix[row].length; column += 1) {
if (matrix[row][column] === expected) return { row, column }
}
}
return null
}
findPosition(
[
[1, 2],
[3, 4]
],
3
) // { row: 1, column: 0 }
NecesidadOpción expresiva
transformar todosmap
seleccionar algunosfilter
encontrar unofind
comprobar alguno/todossome / every
producir un acumuladorreduce o for...of
efectos síncronos simplesforEach
salida temprana o varias reglasfor...of
await secuencialfor...of
const prices = [10, 20, 30]
prices.map((price) => price * 2)
// [20, 40, 60]
const doubled = []
for (const price of prices) {
doubled.push(price * 2)
}
// mismo resultado; más líneas, pero permite control adicional
const saved = []
for (const item of items) {
saved.push(await saveItem(item))
}
saved // resultados en orden, una operación después de otra
const saved = await Promise.all(items.map((item) => saveItem(item)))
saved // resultados en orden de entrada; operaciones iniciadas juntas

forEach(async () => ...) no espera las Promises del callback. Elige secuencia, concurrencia total o un límite de concurrencia según el servicio.

async function* pages() {
yield Promise.resolve(["a", "b"])
yield Promise.resolve(["c"])
}
const items = []
for await (const page of pages()) {
items.push(...page)
}
items // ['a', 'b', 'c']

Es especialmente útil con streams, async generators y fuentes paginadas que producen datos gradualmente.

Eliminar o agregar elementos al mismo array durante la iteración puede saltar valores o extender el ciclo. Si necesitas filtrar, crea otra colección.

const original = [1, 2, 3, 4]
const odd = original.filter((number) => number % 2 !== 0)
odd // [1, 3]
original // [1, 2, 3, 4]

Cuando la mutación local sea intencional, recorre de atrás hacia delante al eliminar por índice para no desplazar elementos pendientes.

Evitar ciclos infinitos y bloquear la interfaz

Sección titulada «Evitar ciclos infinitos y bloquear la interfaz»
  • Comprueba que el contador o estado avance hacia la salida.
  • Define un máximo de intentos para datos externos.
  • No recorras estructuras que pueden crecer sin límite dentro del mismo ciclo.
  • Divide trabajo grande en lotes o muévelo a un worker.
  • Añade cancelación a procesos asíncronos largos.
function findWithLimit(next, maximum = 1_000) {
for (let attempt = 0; attempt < maximum; attempt += 1) {
const value = next()
if (value.done) return value.value
}
throw new RangeError("Se alcanzó el límite de iteraciones")
}