O TypeScript tem os mesmos loops do JavaScript: for, for...of, for...in, while, do...while e o método de array forEach. O que o TypeScript acrescenta são os tipos: a variável de um for...of tem o tipo do elemento, e uma chave de for...in é sempre uma string.
Na maioria dos loops sobre arrays, for...of é a escolha certa: é fácil de ler, aceita break, continue e await e não precisa de anotações.
O for clássico
for (init; condition; update) é o loop para contar, avançar de mais de um em um ou percorrer de trás para frente. O TypeScript infere o contador a partir do valor inicial.
Declare o contador com let, não com var. Com let, cada iteração tem seu próprio binding, então um callback criado dentro do loop enxerga o valor daquela iteração.
for...of: valores de arrays, strings e maps
for...of funciona com qualquer iterável, e a variável do loop recebe o tipo correspondente. Para ter também o índice, percorra array.entries(), que gera tuplas [index, value].
break sai do loop e continue pula para o próximo item, exatamente como em um for. Os detalhes de tempo de execução (iteráveis, generators) estão na página de for...of e for...in do JavaScript.
forEach: sem break, sem await
forEach chama uma função para cada elemento e retorna undefined. Os parâmetros do callback são tipados a partir do array, então não precisam de anotação. Duas coisas que ele não faz:
- Parar antes.
breakdentro do callback é o erro de compilação TS1107,Jump target cannot cross function boundary.Umreturnsó encerra a chamada atual, então funciona comocontinue. - Esperar trabalho assíncrono.
forEachignora a promise que um callbackasyncretorna.
A armadilha do async passa despercebida com facilidade, porque compila sem nenhum aviso:
Os callbacks do forEach terminam na ordem dos timers (10, 20, 30), depois que o loop já seguiu em frente. A versão com for...of os executa em sequência. Para trabalho em paralelo que você ainda precisa esperar, use await Promise.all(items.map(async (item) => { ... })).
for...in: as chaves são strings
for...in percorre os nomes das propriedades enumeráveis de um objeto. O tipo da chave é sempre string, até em arrays, em que as chaves são os índices como strings. Isso atrapalha na hora de indexar um 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; }'.
O TypeScript tipa a chave como string de propósito: em tempo de execução um objeto pode ter mais propriedades do que o tipo lista (a tipagem estrutural permite propriedades extras), e for...in também percorre propriedades enumeráveis herdadas. Então ele não tem como garantir que a chave é "name" | "age". As correções estão na próxima seção. Evite for...in em arrays.
Percorrendo as chaves e os valores de um objeto
Object.entries dá pares [key, value] e é a forma usual de percorrer um objeto. Object.keys retorna string[] pelo mesmo motivo que for...in, então, quando você sabe que o objeto não tem chaves extras, faça uma assertion do tipo da chave.
Para dados cujas chaves não são conhecidas de antemão, tipe o objeto como Record<string, number> ou use um Map; os dois estão na página de dicionário.
while e do...while
while verifica a condição antes de cada passada; do...while executa o corpo uma vez antes de verificar. Eles servem para loops que não percorrem uma coleção, como ler até uma condição ser satisfeita. O narrowing funciona dentro deles: depois de uma verificação !== undefined na condição, a variável fica estreitada no corpo.
Labels, break e continue em loops aninhados
Um break simples sai só do loop mais interno. Para sair de um loop externo, dê um label a ele e use o nome do label:
Qual loop usar
| Objetivo | Loop |
|---|---|
| Cada elemento de um array | for (const x of arr) |
| Elemento e índice | for (const [i, x] of arr.entries()) ou arr.forEach((x, i) => ...) |
| Parar antes | for...of com break, ou find, some, every |
| Aguardar cada passo | for...of com await dentro |
| Criar um novo array | map, filter (não é um loop) |
| Contar ou avançar de n em n | for (let i = 0; ...; i += n) |
| Chaves e valores de objeto | for (const [k, v] of Object.entries(obj)) |
| Entradas de um Map | for (const [k, v] of map) |
| Até uma condição | while |
Perguntas frequentes
Como escrever um loop for no TypeScript?
Do mesmo jeito que no JavaScript: for (let i = 0; i < 5; i++) { ... }; o TypeScript infere i como number. Para percorrer os elementos de um array, for (const item of items) é mais curto, e item recebe o tipo dos elementos do array.
Como sair de um forEach no TypeScript?
Não dá: break dentro do callback é o erro de compilação TS1107 (Jump target cannot cross function boundary), e return só encerra a chamada atual do callback, como um continue. Use for...of com break, ou some/find/every, que param antes por definição.
Qual é a diferença entre for...of e for...in no TypeScript?
for...of percorre os valores de um iterável (elementos de array, caracteres de string, entradas de Map) com seus tipos reais. for...in percorre as chaves enumeráveis de um objeto, sempre com o tipo string, inclusive os índices de arrays como strings. Use for...of para arrays e Object.entries para objetos.
Como percorrer as chaves e os valores de um objeto no TypeScript?
Use for (const [key, value] of Object.entries(obj)). key é uma string e value é a union dos tipos das propriedades. Se você precisa de key com o tipo keyof typeof obj, faça uma assertion: (Object.keys(obj) as (keyof typeof obj)[]), já que o TypeScript tipa Object.keys como string[] de propósito.
Dá para usar await dentro de forEach?
Compila, mas forEach não espera as promises que o callback retorna, então o loop termina antes do trabalho. Use for...of com await dentro para trabalho sequencial, ou await Promise.all(items.map(async (x) => ...)) para rodar em paralelo.