Node.js Generators & Comparați cu apelurile inverse

⚡ Rezumat inteligent

GeneratorFuncțiile s din Node.js sunt funcții a căror execuție poate fi suspendată și reluată la cerere. Cuvântul cheie yield pune funcția în pauză, metoda next o reia, iar această asociere aplatizează apelurile inverse imbricate profund într-un cod secvențial lizibil.

  • ⏸️ Comportament de bază: Un generator se suspendă la fiecare randament și reia exact de unde s-a oprit la următoarea apelare.
  • ✳️ Declaraţie: Un asterisc după cuvântul cheie „funcție” marchează funcția ca generator și nu ca fiind una obișnuită.
  • 🔄 Canal bidirecțional: O valoare transmisă în next devine rezultatul expresiei yield care a pus funcția în pauză.
  • 🪆 Iadul apelurilor inverse: Apelurile inverse imbricate devin ilizibile pe măsură ce se adaugă pași, problemă pentru care au fost adoptați generatorii.
  • 🏃 Alergător necesar: Un generator nu se autopropulsează; o funcție runner trebuie să apeleze next în mod repetat pentru ca munca asincronă să continue.
  • 🦥 Execuție leneșă: Suspendarea și reluarea permit unui program să extragă valori doar în momentul în care sunt necesare.

Node.js GeneratorComparativ cu apelurile inverse

În această prezentare generală, vom analiza generatoarele și cum diferă acestea de apelurile inverse.

Ce sunt generatoarele?

Generatora devenit popular în Node.js datorită a ceea ce sunt capabili să facă.

  • Generators sunt execuții de funcții care pot fi suspendate și reluate la un moment ulterior.
  • GeneratorSunt utile pentru concepte precum „execuție leneșă”. Prin suspendarea execuției și reluarea acesteia după bunul plac, valorile sunt extrase doar atunci când sunt necesare.

Generatorau cele două metode cheie de mai jos.

  1. Metoda randamentului — apelată în interiorul unei funcții pentru a opri execuția exact la linia unde yield apare.
  2. Metoda următoare — apelată din aplicația principală pentru a relua o funcție care a cedat. Execuția continuă până la următoarea yield, sau până la sfârșitul funcției.

Să vedem un exemplu despre cum pot fi utilizate generatoarele. Exemplul definește o funcție simplă de adunare care adună două numere, oprind execuția în puncte diferite pentru a arăta cum se comportă generatoarele.

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 Explicație

  1. Primul pas este definirea funcției generatorului. Rețineți * adăugat la function cuvânt cheie. Funcția este apelată Add și ia un parametru x.
  2. yield cuvântul cheie este specific generatoarelor și întrerupe funcția în timpul execuției. Execuția se oprește aici până când next() este invocat.
  3. Al doilea yield ambele pun funcția în pauză și primesc o valoare: orice este transmisă următorului next() apelul devine valoarea lui y.
  4. apel gen.next() prima dată rulează funcția până la prima yield. Apel gen.next(6) îl reia, atribuie 6 lui y, și se întoarce x + y.

⚠️ Corecție: listarea originală conținea rândul y = 6 imediat dupa var y = yield(null);Acea atribuire suprascrie orice valoare next() provizii, ceea ce anulează scopul exemplului și face ca al doilea yield fără sens. Valoarea este acum transmisă prin gen.next(6), adică modul în care un generator primește de fapt date de la apelant.

Callback-uri vs. generatoare

Generatorrezolvă problema cunoscută sub numele de „iad de apel invers”. Funcțiile de apel invers devin uneori atât de adânc imbricate în timpul dezvoltării unei aplicații Node.js încât sunt dificil de urmărit.

Aici intervin generatoarele. Unul dintre cele mai comune exemple este crearea funcțiilor de temporizare. Exemplul de mai jos construiește o funcție simplă de întârziere și apoi o apelează cu întârzieri de 1000, 2000 și 3000 ms.

Pas 1) Definiți funcția de apel invers cu codul de întârziere necesar.

Reapeluri vs. Generators

function Timedelay(ptime, callback) {

    setTimeout(function() {

        callback("Pausing for " + ptime);

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

Code Explicație

  1. O funcție numită Timedelay este creat cu un parametru ptime, care conține numărul de milisecunde pentru pauză.
  2. setTimeout așteaptă acel număr de milisecunde și apoi invocă apelul invers furnizat.

⚠️ Corecție: codul original a fost transmis time ca al doilea argument pentru setTimeout, dar nicio variabilă numită time există — parametrul este ptimeAșa cum este scris, aruncă ReferenceError: time is not defined.

Pas 2) Acum luați în considerare codul care folosește apeluri inverse. Pentru a înlănțui întârzieri de 1000, 2000 și 3000 de milisecunde, apelurile inverse se imbricau una în cealaltă.

Reapeluri vs. Generators

Timedelay(1000, function(message) {

    console.log(message);

    Timedelay(2000, function(message) {

        console.log(message);

        Timedelay(3000, function(message) {

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

Code Explicație

  1. Timedelay este apelată ca o funcție de apel invers cu valoarea 1000.
  2. În interiorul acelui apel invers, Timedelay este apelat din nou cu 2000.
  3. În interiorul celuilalt, Timedelay este apelat a treia oară cu 3000.

⚠️ Corecție: fiecare apel invers din jurnalul original msg, dar parametrul este denumit messageToate cele trei linii au aruncat ReferenceError: msg is not defined.

Din codul de mai sus puteți vedea că devine mai dezordonat cu fiecare apel suplimentar — această imbricare este ceea ce descrie iadul apelurilor inverse.

Pas 3) Acum vedeți același comportament scris cu generatoare. Secvența se citește ca trei linii plate în loc de trei niveluri de imbricare.

Reapeluri vs. Generators

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

Code Explicație

  1. O funcție generatoare este definită pentru a apela Timedelay Funcția.
  2. yield este folosit cu Timedelay și 1000 ca valoare a parametrului.
  3. yield este folosit din nou cu 2000 și încă o dată cu 3000.

⚠️ Corecție: originalul a scris console,log(...) cu o virgulă în loc de punct pe toate cele trei linii, ceea ce reprezintă o eroare de sintaxă și împiedică analizarea fișierului.

Cum să rulezi a Generator cu o funcție Runner

Generatorul de mai sus se citește frumos, dar nu va face nimic de unul singur. Acesta este detaliul pe care majoritatea explicațiilor îl omit și este motivul pentru care versiunea plată poate părea magică. Un generator este o mașină în pauză și ceva trebuie să apese în mod repetat butonul. Apelarea Messages() pur și simplu creează obiectul generator și returnează imediat valoarea; niciun cronometru nu pornește și nimic nu este afișat.

Piesa lipsă este un alergător: o funcție mică care apelează next(), așteaptă finalizarea lucrului asincron, apoi apelează next() din nou cu rezultatul.

// 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);

Trei puncte fac acest lucru să funcționeze. În primul rând, Timedelay nu mai acceptă direct o funcție de apel invers; returnează o funcție pe care rulantul o poate invoca, redând controlul atunci când cronometrul se declanșează. În al doilea rând, it.next(value) introduce rezultatul anterior în generatorul pus în pauză, ceea ce permite yield să apară în partea dreaptă a unei sarcini. În al treilea rând, alergătorul se oprește imediat ce result.done devine adevărat, deci recursivitatea se termină natural. Acest model de alergare este exact ceea ce au furnizat biblioteci precum co și este mecanismul care async/await absorbită ulterior în limba însăși.

Generators vs async/await: Ce metodă să folosim

Generators erau metoda standard de a scrie cod asincron plat înainte async/await a existat, iar cele două au aceeași idee fundamentală de a pune în pauză o funcție la un moment dat. Node a suportat async/await nativ de la versiunea 7.6, ceea ce modifică recomandarea pentru oricine începe o nouă activitate astăzi.

Aspect Generators asincron/așteaptă
Se caută alergător Da, scris de tine sau de o bibliotecă Nu, încorporat în limbaj
Cuvânt cheie întrerupe yield await
Eroare de manipulare Runner trebuie să transmită erorile Încercare obișnuită și prindere
Se poate întrerupe parțial Da, și poate fi reluat selectiv Nu, rulează până la finalizare
Cel mai bun pentru Secvențe leneșe și fluxuri infinite Muncă asincronă obișnuită

Utilizare async/await pentru flux asincron în codul nou, deoarece elimină generatorul și restabilește funcțiile obișnuite de tip try și catch. Păstrați generatoarele doar pentru ceea ce fac ei bine: producerea de valori în mod leneș, iterarea secvențelor mari sau infinite și orice caz în care consumatorul decide exact când este calculată următoarea valoare.

Comun Generator Erori și cum să le remediați

Majoritatea problemelor generatorului provin din uitarea faptului că funcția este inertă până când ceva o apelează. next(), sau din mici omisiuni sintactice în jurul asteriscului. Fiecare simptom de mai jos își numește cauza și remedierea.

  • Nu se întâmplă nimic când generatorul este apelat: apelarea acestuia returnează un iterator, nu un rezultat. next()sau să o dai unui alergător.
  • Eroare de sintaxă la randament: lipsește asteriscul. yield este valabil doar în interiorul unui function*.
  • Valoarea din next() ajunge nedefinită: prima next() apelul nu poate livra o valoare, deoarece generatorul nu a atins o yield încă. Trimiteți date de la al doilea apel încolo.
  • Bucla infinită într-un alergător: result.done nu este niciodată verificat, deci next() este apelat în mod repetat după sfârșit. Funcția „return” este întotdeauna adevărată atunci când „done”.
  • Erorile dispar în tăcere: O respingere din cadrul pasului asincron nu este niciodată transmisă înapoi în generator. Transmiteți-o înainte cu it.throw(err) astfel încât o comandă try and catch scrisă în generator să o poată vedea și gestiona.

Întrebări frecvente

Un obiect cu două proprietăți: value, păstrând tot ce a fost cedat și done, o valoare booleană care devine adevărată odată ce generatorul termină. Testul alergătorilor este efectuat pentru a ști când să se oprească.

Da. Un generator care conține o buclă infinită este sigur deoarece nimic nu se calculează până când nu este apelată next(). Acest lucru face ca generatoarele să fie potrivite pentru fluxuri de identificatori sau rezultate paginate.

Da. AI instrumentele schimbă yield cu await și abandonează complet alergătorul. RevPriviți cu atenție secvențele lenny, deoarece un generator care nu se termină niciodată nu are un echivalent direct în modul async await.

Asistenți AI trace. dacă alergătorul a încetat să mai apeleze, dacă nu a fost niciodată invocat un apel invers de reluare sau dacă nu s-a ajuns niciodată la finalizare. Aceste trei motive explică aproape fiecare generator blocat.

Nu. Se declară un generator asincron async function* și este consumată cu for await...ofCombină ambele idei și este potrivit pentru transmiterea în flux asincronă a datelor, cum ar fi cursorii bazei de date.

Rezumați această postare cu: