TypeScript tiene los mismos bucles que JavaScript: for, for...of, for...in, while, do...while y el método de array forEach. Lo que añade TypeScript son los tipos: la variable de un for...of tiene el tipo de los elementos, y una clave de for...in siempre es un string.
Para la mayoría de bucles sobre arrays, for...of es el que conviene: se lee bien, admite break, continue y await, y no necesita anotaciones.
El bucle for clásico
for (init; condition; update) es el bucle para contar, avanzar de más de uno en uno o recorrer hacia atrás. TypeScript infiere el contador a partir de su valor inicial.
Declara el contador con let, no con var. Con let, cada iteración tiene su propia variable, así que un callback creado dentro del bucle ve el valor de esa iteración.
for...of: valores de arrays, strings y maps
for...of funciona con cualquier iterable, y la variable del bucle recibe el tipo correspondiente. Para tener también el índice, recorre array.entries(), que produce tuplas [index, value].
break sale del bucle y continue salta al siguiente elemento, exactamente como en un bucle for. Los detalles de ejecución (iterables, generadores) están en la página de JavaScript sobre for...of y for...in.
forEach: sin break ni await
forEach llama a una función por cada elemento y devuelve undefined. Los parámetros del callback se tipan a partir del array, así que no hacen falta anotaciones. Hay dos cosas que no puede hacer:
- Detenerse antes.
breakdentro del callback es el error de compilación TS1107,Jump target cannot cross function boundary.Unreturnsolo termina la llamada actual, así que actúa comocontinue. - Esperar trabajo asíncrono.
forEachignora la promesa que devuelve un callbackasync.
La trampa asíncrona es fácil de pasar por alto, porque compila sin ningún aviso:
Los callbacks de forEach terminan en el orden de los temporizadores (10, 20, 30) cuando el bucle ya ha seguido adelante. La versión con for...of los ejecuta en secuencia. Para trabajo en paralelo que igualmente quieres esperar, usa await Promise.all(items.map(async (item) => { ... })).
for...in: las claves son strings
for...in recorre los nombres de las propiedades enumerables de un objeto. El tipo de la clave siempre es string, incluso en arrays, donde las claves son los índices en forma de string. Eso lo hace incómodo para indexar un objeto tipado:
index.ts(5,22): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ name: string; age: number; }'.
No index signature with a parameter of type 'string' was found on type '{ name: string; age: number; }'.
TypeScript tipa la clave como string a propósito: en tiempo de ejecución un objeto puede tener más propiedades de las que indica su tipo (el tipado estructural permite propiedades de más), y for...in también recorre las propiedades enumerables heredadas. Así que no puede garantizar que la clave sea "name" | "age". Las soluciones están en la siguiente sección. Evita for...in en arrays por completo.
Recorrer las claves y los valores de un objeto
Object.entries da pares [key, value] y es la forma habitual de recorrer un objeto. Object.keys devuelve string[] por el mismo motivo que for...in, así que cuando sabes que el objeto no tiene claves de más, haz una aserción del tipo de la clave.
Para datos cuyas claves no se conocen de antemano, tipa el objeto como Record<string, number> o usa un Map; los dos se explican en la página de diccionarios.
while y do...while
while comprueba su condición antes de cada pasada; do...while ejecuta el cuerpo una vez antes de comprobarla. Sirven para bucles que no recorren una colección, como leer hasta que se cumpla una condición. El estrechamiento funciona dentro de ellos: tras una comprobación !== undefined en la condición, la variable queda estrechada en el cuerpo.
Etiquetas, break y continue en bucles anidados
Un break simple solo sale del bucle más interno. Para salir de un bucle exterior, ponle una etiqueta y nómbrala:
Qué bucle usar
| Objetivo | Bucle |
|---|---|
| Cada elemento de un array | for (const x of arr) |
| Elemento e índice | for (const [i, x] of arr.entries()) o arr.forEach((x, i) => ...) |
| Detenerse antes | for...of con break, o find, some, every |
| Esperar cada paso | for...of con await dentro |
| Construir un array nuevo | map, filter (no un bucle) |
| Contar o avanzar de n en n | for (let i = 0; ...; i += n) |
| Claves y valores de un objeto | for (const [k, v] of Object.entries(obj)) |
| Entradas de un Map | for (const [k, v] of map) |
| Hasta que se cumpla una condición | while |
Preguntas frecuentes
¿Cómo se escribe un bucle for en TypeScript?
Igual que en JavaScript: for (let i = 0; i < 5; i++) { ... }; TypeScript infiere i como number. Para recorrer los elementos de un array, for (const item of items) es más corto, y item recibe el tipo de los elementos del array.
¿Cómo salgo de un forEach en TypeScript?
No se puede: break dentro del callback es el error de compilación TS1107 (Jump target cannot cross function boundary), y return solo termina la llamada actual del callback, como continue. Usa for...of con break, o some/find/every, que se detienen antes por diseño.
¿Qué diferencia hay entre for...of y for...in en TypeScript?
for...of recorre los valores de un iterable (elementos de un array, caracteres de un string, entradas de un Map) con sus tipos reales. for...in recorre las claves de propiedades enumerables de un objeto, siempre con tipo string, incluidos los índices de un array como strings. Usa for...of para arrays y Object.entries para objetos.
¿Cómo recorro las claves y los valores de un objeto en TypeScript?
Usa for (const [key, value] of Object.entries(obj)). key es un string y value es la unión de los tipos de las propiedades. Si necesitas key con tipo keyof typeof obj, haz una aserción: (Object.keys(obj) as (keyof typeof obj)[]), ya que TypeScript tipa Object.keys como string[] a propósito.
¿Se puede usar await dentro de forEach?
Compila, pero forEach no espera a las promesas que devuelve el callback, así que el bucle termina antes que el trabajo. Usa for...of con await dentro para trabajo secuencial, o await Promise.all(items.map(async (x) => ...)) para ejecutarlo en paralelo.