Node.js Generators & Confronta con richiamate

โšก Riepilogo intelligente

GeneratorIn Node.js, le funzioni `yield` sono funzioni la cui esecuzione puรฒ essere sospesa e ripresa a richiesta. La parola chiave `yield` mette in pausa la funzione, il metodo `next` la riprende, e questa combinazione semplifica le callback annidate in un codice sequenziale piรน leggibile.

  • โธ๏ธ Comportamento fondamentale: Un generatore si sospende a ogni chiamata di uscita e riprende esattamente da dove si era interrotto alla successiva chiamata.
  • ๏ธ Dichiarazione: Un asterisco dopo la parola chiave `function` indica che la funzione รจ un generatore anzichรฉ una funzione ordinaria.
  • ๐Ÿ”„ Canale bidirezionale: Il valore passato a next diventa il risultato dell'espressione yield che ha messo in pausa la funzione.
  • ๐Ÿช† Inferno delle richiamate: Le callback annidate diventano illeggibili con l'aggiunta di passaggi, ed รจ proprio questo il problema che i generatori sono stati adottati per risolvere.
  • ๐Ÿƒ Si ricerca un corridore: Un generatore non si autoalimenta; una funzione di esecuzione deve richiamare ripetutamente `next` affinchรฉ il lavoro asincrono possa procedere.
  • ๐Ÿฆฅ Esecuzione approssimativa: La sospensione e la ripresa consentono a un programma di recuperare i valori solo nel momento in cui sono necessari.

Node.js Generators Rispetto alle chiamate di ritorno

In questa guida esamineremo i generatori e le differenze rispetto alle callback.

Cosa sono i generatori?

Generators divenne popolare in Node.js per via di ciรฒ che sono capaci di fare.

  • Generatorsono esecuzioni di funzioni che possono essere sospese e riprese in un secondo momento.
  • GeneratorLe s sono utili per concetti come "esecuzione pigra". Sospendendo l'esecuzione e riprendendola a piacimento, i valori vengono prelevati solo quando sono necessari.

Generators hanno i due metodi principali descritti di seguito.

  1. Metodo di rendimento โ€” chiamata all'interno di una funzione per interrompere l'esecuzione esattamente sulla riga in cui yield appare.
  2. Metodo successivo โ€” chiamata dall'applicazione principale per riprendere una funzione che ha ceduto il controllo. L'esecuzione continua fino al successivo yield, o fino alla fine della funzione.

Vediamo un esempio di come si possono usare i generatori. L'esempio definisce una semplice funzione Add che somma due numeri, interrompendo l'esecuzione in diversi punti per mostrare come si comportano i generatori.

Node.js Generators

function* Add(x) {
    yield x + 1;
    // The value passed into the second next() call lands here
    var y = yield(null);
    return x + y;
}

var gen = Add(5);

gen.next();      // runs to the first yield, returns { value: 6, done: false }
gen.next(6);     // resumes, y becomes 6, returns { value: 11, done: true }

Code Spiegazione

  1. Il primo passo รจ definire la funzione generatrice. Notare la * aggiunto al function parola chiave. La funzione si chiama Add e accetta un parametro x.
  2. Migliori yield La parola chiave รจ specifica dei generatori e mette in pausa la funzione a metร  esecuzione. L'esecuzione si interrompe qui fino a quando next() viene invocato.
  3. La seconda yield entrambi mettono in pausa la funzione e ricevono un valore: qualunque cosa venga passata al seguente next() la chiamata diventa il valore di y.
  4. chiamata gen.next() la prima volta esegue la funzione fino al primo yield. chiamata gen.next(6) lo riprende, assegna 6 a y, e restituisce x + y.

โš ๏ธ Correzione: l'elenco originale conteneva la riga y = 6 subito dopo var y = yield(null);Tale assegnazione sovrascrive qualsiasi valore next() forniture, il che vanifica lo scopo dell'esempio e rende il secondo yield senza senso. Il valore viene ora trasmesso attraverso gen.next(6), che รจ il modo in cui un generatore riceve effettivamente i dati dal chiamante.

Richiamate vs generatori

Generators risolvono il problema noto come callback hell. Le funzioni di callback a volte diventano cosรฌ annidate in profonditร  durante lo sviluppo di un'applicazione Node.js che risultano difficili da seguire.

รˆ qui che entrano in gioco i generatori. Uno degli esempi piรน comuni รจ la creazione di funzioni timer. L'esempio seguente crea una semplice funzione di ritardo e la richiama con ritardi di 1000, 2000 e 3000 ms.

Passo 1) Definisci la funzione di callback con il codice di ritardo necessario.

Richiamate vs. Generators

function Timedelay(ptime, callback) {

    setTimeout(function() {

        callback("Pausing for " + ptime);

    }, ptime);   // the delay must use ptime, the parameter name
}

Code Spiegazione

  1. Una funzione chiamata Timedelay viene creato con un parametro ptime, che indica il numero di millisecondi di pausa.
  2. setTimeout attende quel numero di millisecondi e poi richiama la funzione di callback fornita.

โš ๏ธ Correzione: il codice originale รจ passato time come secondo argomento a setTimeout, ma nessuna variabile denominata time esiste โ€” il parametro รจ ptime. Cosรฌ come รจ scritto, lancia ReferenceError: time is not defined.

Passo 2) Consideriamo ora il codice che utilizza le callback. Per concatenare ritardi di 1000, 2000 e 3000 millisecondi, le callback si annidano l'una nell'altra.

Richiamate vs. Generators

Timedelay(1000, function(message) {

    console.log(message);

    Timedelay(2000, function(message) {

        console.log(message);

        Timedelay(3000, function(message) {

            console.log(message);
        });
    });
});

Code Spiegazione

  1. Timedelay viene chiamata come funzione di callback con il valore 1000.
  2. All'interno di quella chiamata, Timedelay viene richiamato con 2000.
  3. Dentro quello, Timedelay viene chiamata una terza volta con 3000.

โš ๏ธ Correzione: ogni callback nel log originale msg, ma il parametro รจ denominato messageTutte e tre le linee hanno lanciato ReferenceError: msg is not defined.

Dal codice qui sopra si puรฒ notare che la situazione si complica con ogni chiamata aggiuntiva: questo annidamento รจ ciรฒ che viene descritto come "callback hell".

Passo 3) Ora osserviamo lo stesso comportamento rappresentato con i generatori. La sequenza si legge come tre linee piatte anzichรฉ come tre livelli di annidamento.

Richiamate vs. Generators

function* Messages() {
    console.log(yield Timedelay(1000));
    console.log(yield Timedelay(2000));
    console.log(yield Timedelay(3000));
}

Code Spiegazione

  1. รˆ definita una funzione generatrice per chiamare la Timedelay funzione.
  2. yield รจ usato con Timedelay e 1000 come valore del parametro.
  3. yield viene utilizzato di nuovo con 2000 e ancora una volta con 3000.

โš ๏ธ Correzione: l'originale ha scritto console,log(...) con una virgola al posto del punto su tutte e tre le righe, il che costituisce un errore di sintassi e impedisce completamente l'analisi del file.

Come eseguire un Generator con funzione di corsa

Il generatore qui sopra รจ ben fatto, ma non farร  nulla da solo. Questo รจ il dettaglio che la maggior parte delle spiegazioni omette, ed รจ il motivo per cui la versione piatta puรฒ sembrare magica. Un generatore รจ una macchina in pausa e qualcosa deve continuare a premere il pulsante. Chiamando Messages() Si limita a creare l'oggetto generatore e a restituire immediatamente il risultato; non si avvia alcun timer e non viene stampato nulla.

Il pezzo mancante รจ un runner: una piccola funzione che chiama next(), attende che il lavoro asincrono termini e poi chiama next() di nuovo con il risultato.

// Timedelay now hands its result back to the runner
function Timedelay(ptime) {
    return function(resume) {
        setTimeout(function() {
            resume("Pausing for " + ptime);
        }, ptime);
    };
}

// The runner drives the generator to completion
function run(generatorFunction) {
    var it = generatorFunction();

    function step(value) {
        var result = it.next(value);
        if (result.done) return;
        // result.value is the function returned by Timedelay
        result.value(step);
    }

    step();
}

run(Messages);

Tre punti fanno sรฌ che questo funzioni. Primo, Timedelay non accetta piรน direttamente una callback; restituisce una funzione che il runner puรฒ invocare, restituendo il controllo quando il timer si attiva. In secondo luogo, it.next(value) alimenta il risultato precedente nel generatore in pausa, che รจ ciรฒ che permette yield per apparire sul lato destro di un incarico. Terzo, il corridore si ferma non appena result.done diventa vero, quindi la ricorsione termina naturalmente. Questo modello di runner รจ esattamente ciรฒ che librerie come co hanno fornito, ed รจ il meccanismo che async/await successivamente assorbito nella lingua stessa.

Generators vs async/await: quale usare?

Generatorprima erano il modo standard per scrivere codice asincrono piatto async/await esistevano e i due condividono la stessa idea di base di mettere in pausa una funzione a metร . Node ha supportato async/await nativamente dalla versione 7.6, il che modifica la raccomandazione per chiunque inizi un nuovo lavoro oggi.

Aspetto Generators asincrono / attendono
Runner cercasi Sรฌ, scritto da te o da una biblioteca No, integrato nel linguaggio
Pausa parola chiave yield await
Gestione degli errori Il corridore deve inoltrare gli errori Prova e prendi
รˆ possibile interrompere parzialmente Sรฌ, e puรฒ essere ripreso in modo selettivo No, corre fino al completamento
migliori per Sequenze pigre e flussi infiniti Lavoro asincrono ordinario

Usa il async/await Per il flusso asincrono nel nuovo codice, poichรฉ rimuove il runner e ripristina il normale try-catch. Riservate i generatori a ciรฒ che solo loro sanno fare bene: produrre valori in modo lazy, iterare sequenze grandi o infinite e in qualsiasi caso in cui il consumatore decida esattamente quando viene calcolato il valore successivo.

Uncommon Generator Errori e come risolverli

La maggior parte dei problemi dei generatori deriva dal dimenticare che la funzione รจ inerte finchรฉ qualcosa non la chiama next(), o da piccoli errori di sintassi intorno all'asterisco. Ogni sintomo riportato di seguito ne indica la causa e la soluzione.

  • Non succede nulla quando si richiama il generatore: La chiamata restituisce un iteratore, non un risultato. Chiama next(), oppure passarlo a un corridore.
  • Errore di sintassi su yield: L'asterisco รจ mancante. yield รจ valido solo all'interno di un function*.
  • Il valore restituito da next() risulta indefinito: il primo next() la chiamata non puรฒ fornire un valore, perchรฉ il generatore non ha raggiunto un yield ancora. Invia i dati dalla seconda chiamata in poi.
  • Ciclo infinito in un gioco di corsa: result.done non viene mai controllato, quindi next() Viene chiamata ripetutamente anche dopo la fine. Il metodo Always Return When Done รจ true.
  • Gli errori scompaiono silenziosamente: un rifiuto all'interno del passaggio asincrono non viene mai passato al generatore. Inoltralo con it.throw(err) in modo che un blocco try-catch scritto all'interno del generatore possa effettivamente vederlo e gestirlo.

DOMANDE FREQUENTI

Un oggetto con due proprietร : value, trattenendo tutto ciรฒ che era stato prodotto, e done, un valore booleano che diventa vero una volta che il generatore ha terminato. I runner testano per sapere quando fermarsi.

Sรฌ. Un generatore che contiene un ciclo infinito รจ sicuro perchรฉ non viene eseguito alcun calcolo finchรฉ non viene chiamata la funzione next(). Questo rende i generatori particolarmente adatti a flussi di identificatori o risultati paginati.

Sรฌ. Le serrature scorrevoli portatili e i catenacci a superficie possono essere usati per mettere in sicurezza una porta a scomparsa dall'esterno. Alcuni kit con catena di sicurezza consentono anche il bloccaggio esterno con chiave o manopola girevole. AI Gli strumenti sostituiscono yield con await e abbandonano completamente il runner. RevEsamina attentamente le sequenze lazy, perchรฉ un generatore che non termina mai non ha un equivalente async await diretto.

Assistenti AI trace se il runner ha smesso di chiamare next, se non รจ mai stata invocata una callback di ripresa o se non รจ mai stato raggiunto done. Questi tre fattori spiegano quasi tutti i generatori bloccati.

No. Viene dichiarato un generatore asincrono. async function* e viene consumato con for await...ofUnisce entrambe le idee ed รจ adatto allo streaming di dati asincroni come i cursori di database.

Riassumi questo post con: