Iteratori e Generatori Asincroni in JavaScript
Impara gli iteratori e i generatori asincroni in JavaScript: Symbol.asyncIterator, async function*, for await...of e un esempio di fetch paginata lazy con codice eseguibile.
La programmazione asincrona è un pilastro fondamentale dello sviluppo moderno in JavaScript, che consente agli sviluppatori di scrivere codice non bloccante e concorrente in grado di gestire in modo efficiente attività come richieste di rete, I/O su file e timer. Questa guida tratta gli iteratori asincroni e i generatori asincroni, due funzionalità introdotte in ECMAScript 2018 che permettono di iterare su dati che arrivano nel tempo — un blocco per ogni round-trip di rete, un evento per ogni azione dell'utente — senza bloccare il resto del programma.
Questa pagina presuppone che tu abbia familiarità con gli iterabili, i generatori e le promise. Se qualcuno di questi argomenti è nuovo, leggilo prima.
Comprendere gli Iteratori Asincroni
Cosa sono gli Iteratori Asincroni?
Gli iteratori asincroni sono un tipo speciale di iteratore progettato per gestire flussi di dati asincroni. A differenza degli iteratori tradizionali, che operano in modo sincrono, gli iteratori asincroni consentono agli sviluppatori di iterare su sequenze di valori asincroni, come promise o stream, in modo non bloccante.
Tecnicamente, un object viene considerato un iterabile asincrono se implementa il metodo Symbol.asyncIterator, che restituisce un object iteratore asincrono. Ecco un esempio pratico di implementazione manuale di questa interfaccia su un object personalizzato:
Iteratori Sincroni vs. Iteratori Asincroni
La differenza tra un iterabile ordinario e uno asincrono si riduce a tre aspetti: il nome del metodo, il tipo restituito da next() e il ciclo utilizzato per consumarlo.
| Iterabile sincrono | Iterabile asincrono | |
|---|---|---|
| Metodo | Symbol.iterator | Symbol.asyncIterator |
next() restituisce | { value, done } | una Promise di { value, done } |
| Ciclo di consumo | for...of | for await...of |
| Sintassi del generatore | function* | async function* |
Poiché nel caso asincrono next() restituisce una Promise, ogni passo del ciclo può attendere un'operazione asincrona — una fetch, un timer, una lettura dal database — prima che venga prodotto il valore successivo. Un semplice for...of non può farlo: si aspetta che value/done siano disponibili immediatamente. Usare for await...of su un iterabile solo sincrono funziona comunque (il motore avvolge i valori in promise già risolte), ma usare un for...of sincrono su un iterabile asincrono non funziona — si itererebbe semplicemente su object Promise in attesa.
Come Usare gli Iteratori Asincroni
Per sfruttare gli iteratori asincroni nel codice JavaScript, è necessario innanzitutto comprenderne i concetti fondamentali e la sintassi. Esploriamo un semplice esempio per illustrare il funzionamento degli iteratori asincroni in pratica:
In questo esempio definiamo una funzione generatrice asincrona generateNumbers() che produce una sequenza di numeri in modo asincrono. Creiamo quindi un iterabile asincrono dalla funzione generatrice e utilizziamo un ciclo for await...of per iterare sui valori prodotti dall'iteratore asincrono.
Nota: Quando si usa yield con un valore semplice all'interno di una async function*, il metodo next() dell'iteratore restituisce automaticamente una Promise che si risolve in { value: <il tuo valore>, done: false }. È necessario fare yield di una Promise esplicitamente solo se si vuole che il consumatore riceva un object Promise anziché il valore risolto.
Applicazioni nel Mondo Reale degli Iteratori Asincroni
Gli iteratori asincroni trovano ampia applicazione in scenari che coinvolgono l'elaborazione asincrona dei dati, come il recupero di dati da API esterne, la lettura da stream o la gestione di eventi asincroni. La loro versatilità ed efficienza li rende strumenti indispensabili per i moderni sviluppatori JavaScript che desiderano scrivere applicazioni scalabili e reattive.
Esplorare i Generatori JavaScript
Introduzione ai Generatori
I generatori sono una funzionalità potente introdotta in ECMAScript 2015 che consente la creazione di sequenze iterabili con logica di iterazione personalizzata. A differenza delle funzioni tradizionali, che vengono eseguite fino al completamento al momento dell'invocazione, i generatori possono mettere in pausa e riprendere la propria esecuzione, consentendo la valutazione pigra dei valori.
È importante distinguere tra generatori standard e generatori asincroni:
- Generatori Standard (
function*): Producono valori in modo sincrono. - Generatori Asincroni (
async function*): Restituiscono una Promise da ogni chiamata anext(), consentendo al consumatore di attendere ogni valore utilizzandofor await...of.
Sfruttare i Generatori per la Programmazione Asincrona
Uno degli use case più convincenti per i generatori è la programmazione asincrona. Combinando generatori e promise, gli sviluppatori possono creare flussi di lavoro asincroni che sono allo stesso tempo eleganti e facili da ragionare. Ecco un esempio moderno che utilizza un generatore asincrono per recuperare e produrre dati da un server remoto:
In questo esempio definiamo una funzione generatrice asincrona fetchTodos() che recupera dati in modo asincrono da un'API remota usando la funzione fetch(). Usando await all'interno del generatore e producendo i singoli elementi, possiamo inviare in streaming i risultati direttamente in un ciclo for await...of senza chiamate manuali a .next() o concatenamento di promise.
Fetch Paginata con un Generatore Asincrono
Il pattern che fa brillare i generatori asincroni è la paginazione pigra. Molte API restituiscono i risultati in pagine e richiedono di continuare a richiedere la pagina successiva fino a quando non ce ne sono altre. Un generatore asincrono può nascondere tutta questa gestione contabile: recupera una pagina, produce i suoi elementi uno per uno e richiede la pagina successiva solo quando il consumatore ne ha bisogno. Il chiamante può interrompersi prima — per esempio dopo aver trovato ciò di cui ha bisogno — e non vengono effettuate ulteriori richieste di rete.
Nota il break: poiché il generatore è pigro, uscire dal ciclo dopo 25 elementi significa che il generatore non richiede mai la pagina 3. Questo è ciò che distingue un generatore asincrono dal recuperare tutto in anticipo in un array — si paga solo per i dati effettivamente utilizzati.
Pattern Avanzati con i Generatori
I generatori offrono una moltitudine di pattern e tecniche avanzati per risolvere problemi di programmazione complessi. Ecco alcuni esempi che mostrano la loro versatilità:
- Esecuzione Parallela: Avviando più generatori e gestendo le loro promise in modo concorrente, è possibile eseguire più attività asincrone contemporaneamente.
- Gestione degli Errori: Utilizza blocchi
try-catchall'interno dei generatori per gestire in modo elegante le promise rifiutate prodotte durante il processo di iterazione. - Pipeline di Dati: Costruisci pipeline di elaborazione dati concatenando generatori tra loro, dove l'output di un generatore funge da input per il successivo.
Conclusione
In conclusione, gli iteratori asincroni e i generatori sono strumenti indispensabili nell'arsenale del moderno sviluppatore JavaScript. Padroneggiando queste potenti funzionalità, puoi sbloccare nuove dimensioni di espressività ed efficienza nel tuo codice asincrono. Che tu stia costruendo applicazioni web, API lato server o utility da riga di comando, gli iteratori asincroni e i generatori ti consentono di affrontare con facilità complesse sfide asincrone. Inizia oggi stesso a incorporare iteratori asincroni e generatori nei tuoi progetti JavaScript ed eleva le tue competenze di programmazione a nuovi livelli!
Argomenti Correlati
- Iterabili — la base sincrona dietro
for...ofeSymbol.iterator. - Generatori — la sintassi
function*su cui si basano i generatori asincroni. - Promise — ciò che ogni
awaitall'interno di un generatore asincrono risolve. - Async/await — la sintassi con cui si abbina
for await...of.