# Gli errori più comuni con forEach in TypeScript

> forEach sembra il metodo più semplice degli array, ma in TypeScript nasconde trappole reali: callback async che TypeScript non segnala, narrowing perso nelle closure, indici impliciti any.

- Autore / Author: Diego Betto (https://diegobetto.com/about/)
- Pubblicato / Published: 2026-09-19
- URL: https://diegobetto.com/typescript-foreach-errori/
- Tag: typescript, javascript, errori

`Array.prototype.forEach` è probabilmente il metodo che si impara prima negli array JavaScript, e per questo viene usato anche dove non è la scelta giusta. In TypeScript, in particolare, ci sono almeno tre trappole che il type checker non segnala come ti aspetteresti — non per un bug del compilatore, ma per come sono progettate le sue regole.

### 1. Un callback `async` in forEach non dà errore (e dovrebbe preoccuparti)

Questo è il caso più insidioso perché **compila senza avvisi**:

```typescript
async function processAll(items: string[]) {
  items.forEach(async (item) => {
    await saveToDatabase(item); // eseguito, ma nessuno aspetta questo
  });
  console.log("fatto"); // stampato PRIMA che i salvataggi finiscano
}
```

La firma di `forEach` si aspetta una callback che ritorna `void`. Una funzione `async` ritorna sempre una `Promise`, quindi tecnicamente il tipo di ritorno non è `void` — eppure TypeScript non protesta. Il motivo è una regola specifica del linguaggio: quando il tipo di ritorno atteso è `void`, TypeScript permette che la funzione fornita ritorni **qualsiasi cosa**, a patto che il valore venga ignorato. È una scelta di design pensata per casi come gli event handler (`onClick={() => setCount(c => c + 1)}` dove il valore di ritorno non conta), ma ha l'effetto collaterale di rendere invisibile un bug reale: `forEach` non aspetta le Promise, quindi le chiamate a `saveToDatabase` partono tutte più o meno insieme, senza controllo sull'ordine, e un eventuale rejection non viene catturato da nessun `try/catch` esterno.

**La correzione**: se serve `await` dentro il loop, `forEach` è lo strumento sbagliato. Usa `for...of`:

```typescript
async function processAll(items: string[]) {
  for (const item of items) {
    await saveToDatabase(item);
  }
  console.log("fatto"); // ora è vero
}
```

Oppure, se vuoi eseguire in parallelo ma comunque aspettare il completamento:

```typescript
await Promise.all(items.map((item) => saveToDatabase(item)));
```

La regola `@typescript-eslint/no-misused-promises` (disponibile anche via `eslint-plugin-oxlint`, se usi [oxlint](https://diegobetto.com/oxlint-linter-guida/)) esiste apposta per intercettare questo pattern, perché il compilatore da solo non lo fa.

### 2. `return` dentro forEach non è un `break`

Un errore concettuale comune per chi arriva da un linguaggio con cicli `for` classici:

```typescript
function findFirstEven(numbers: number[]) {
  numbers.forEach((n) => {
    if (n % 2 === 0) {
      return n; // NON esce dal forEach, solo dall'iterazione corrente
    }
  });
}
```

TypeScript non segnala nulla di sbagliato qui — sintatticamente è tutto corretto, semplicemente `return` dentro la callback si comporta come un `continue`, non come un `break`. Il valore ritornato viene scartato (coerentemente con il punto 1: `forEach` si aspetta `void`), e il ciclo continua fino all'ultimo elemento anche se hai già trovato quello che cercavi.

**La correzione**: se serve un'uscita anticipata, `forEach` non la supporta. Serve un `for...of` con `break`, oppure `Array.prototype.find`:

```typescript
const firstEven = numbers.find((n) => n % 2 === 0);
```

### 3. Il narrowing si perde dentro la callback

Un caso meno noto ma reale, legato a come TypeScript ragiona sui confini di funzione:

```typescript
function process(value: string | undefined, items: string[]) {
  if (typeof value === "undefined") return;

  items.forEach(() => {
    console.log(value.toUpperCase()); // può dare errore qui
  });
}
```

Se `value` non è marcato `const` (o se TypeScript non può garantire che non venga riassegnato prima che la callback venga eseguita — cosa che con una callback asincrona è impossibile escludere in generale), il narrowing fatto dall'`if` sopra non è garantito valere dentro una funzione annidata come la callback di `forEach`. Con `strict: true` — default da TypeScript 7, di cui ho parlato nell'articolo su [TypeScript 7](https://diegobetto.com/typescript-7-novita/) — questi casi emergono più spesso di quanto capitasse con la configurazione permissiva di TypeScript 6.

**La correzione**: assegna il valore narrowed a una nuova variabile `const` prima di passarlo alla callback, così il tipo resta fissato:

```typescript
function process(value: string | undefined, items: string[]) {
  if (typeof value === "undefined") return;

  const narrowedValue = value; // const: TypeScript ora può fidarsi

  items.forEach(() => {
    console.log(narrowedValue.toUpperCase());
  });
}
```

### La lezione comune ai tre casi

In tutti e tre gli esempi, TypeScript non sta segnalando un errore di sintassi o di tipo in senso stretto — sta semplicemente non proteggendoti da un errore _logico_ che il sistema di tipi non è progettato per catturare. `forEach` è un'API più limitata di quanto sembri: niente uscita anticipata, niente gestione nativa di async, e closure che possono comportarsi diversamente da quanto ci si aspetta. Per la maggior parte dei casi in cui verrebbe naturale usarlo, `for...of`, `map`, `find` o `Promise.all` sono scelte più esplicite — e più difficili da usare male.
