Generatori JavaScript

Edoardo Midali
Edoardo Midali

I generatori in JavaScript sono funzioni speciali che possono essere messe in pausa e riprese durante la loro esecuzione. A differenza delle funzioni normali che eseguono tutto il codice dall’inizio alla fine, i generatori permettono di fermarsi in punti specifici e continuare successivamente, mantenendo il loro stato interno.

Come Funzionano i Generatori

Un generatore si riconosce dalla sintassi function* (con l’asterisco) e utilizza la parola chiave yield per mettere in pausa l’esecuzione. Quando chiamiamo un generatore, non esegue immediatamente il codice, ma restituisce un oggetto iteratore che possiamo controllare manualmente.

function* contatoreBase() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = contatoreBase();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

Ogni chiamata a next() fa riprendere l’esecuzione fino al prossimo yield, restituendo un oggetto con il valore corrente e lo stato di completamento.

La Parola Chiave yield

yield è il cuore dei generatori. Quando il generatore incontra yield, si ferma e restituisce il valore specificato. La prossima volta che chiamiamo next(), l’esecuzione riprende dalla riga successiva al yield.

function* esempioyield() {
  console.log("Inizio");
  yield "primo valore";
  console.log("Tra primo e secondo");
  yield "secondo valore";
  console.log("Fine");
  return "terminato";
}

Il generatore può anche ricevere valori dall’esterno tramite il parametro di next():

function* comunicazione() {
  const messaggio = yield "Dimmi qualcosa";
  yield `Hai detto: ${messaggio}`;
}

const gen = comunicazione();
console.log(gen.next()); // { value: "Dimmi qualcosa", done: false }
console.log(gen.next("Ciao")); // { value: "Hai detto: Ciao", done: false }

Generatori Infiniti

Una delle caratteristiche più potenti dei generatori è la capacità di creare sequenze infinite senza problemi di memoria, perché i valori vengono calcolati solo quando richiesti:

function* numeriInfiniti() {
  let n = 0;
  while (true) {
    yield n++;
  }
}

const numeri = numeriInfiniti();
console.log(numeri.next().value); // 0
console.log(numeri.next().value); // 1
console.log(numeri.next().value); // 2
// Può continuare all'infinito

Utilizzi Pratici

Sequenze Matematiche

I generatori sono perfetti per implementare sequenze matematiche come Fibonacci:

function* fibonacci() {
  let a = 0,
    b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

// Prendi i primi 10 numeri di Fibonacci
const fib = fibonacci();
const primi10 = [];
for (let i = 0; i < 10; i++) {
  primi10.push(fib.next().value);
}
console.log(primi10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

ID Univoci

Creazione di identificatori univoci progressivi:

function* generatoreID() {
  let id = 1;
  while (true) {
    yield `user_${id++}`;
  }
}

const idGen = generatoreID();
const user1 = idGen.next().value; // "user_1"
const user2 = idGen.next().value; // "user_2"

Processamento Step-by-Step

I generatori sono utili per processare operazioni complesse un passo alla volta:

function* elaborazioneDati(dati) {
  yield "Validazione in corso...";
  // Simula validazione
  yield "Trasformazione in corso...";
  // Simula trasformazione
  yield "Salvataggio in corso...";
  // Simula salvataggio
  return "Elaborazione completata";
}

Integrazione con for…of

I generatori sono automaticamente iterabili, quindi funzionano perfettamente con i cicli for...of:

function* colori() {
  yield "rosso";
  yield "verde";
  yield "blu";
}

for (const colore of colori()) {
  console.log(colore); // rosso, verde, blu
}

// Anche convertibili in array
const arrayColori = [...colori()]; // ["rosso", "verde", "blu"]

Vantaggi Principali

Lazy Evaluation: I valori vengono calcolati solo quando necessari, risparmiando memoria e CPU per sequenze grandi o infinite.

Controllo del Flusso: Possibilità di mettere in pausa e riprendere l’esecuzione, utile per operazioni asincrone o step-by-step.

Stato Mantenuto: Il generatore ricorda dove si è fermato e tutte le variabili locali tra una chiamata e l’altra.

Sintassi Pulita: Più leggibile rispetto alla creazione manuale di iteratori personalizzati.

Limitazioni

I generatori non sono adatti per tutte le situazioni. Sono più utili quando hai bisogno di sequenze, iterazioni personalizzate o controllo fine del flusso di esecuzione. Per operazioni semplici o quando hai bisogno di tutti i valori contemporaneamente, gli array tradizionali potrebbero essere più appropriati.

I generatori rappresentano uno strumento potente per scrivere codice più elegante e efficiente quando si lavora con sequenze di dati o processi che possono essere suddivisi in step.