Trattare con la piramide di callback node.js


9

Ho appena iniziato a utilizzare il nodo e una cosa che ho notato rapidamente è la velocità con cui i callback possono raggiungere un livello di rientro stupido:

doStuff(arg1, arg2, function(err, result) {
    doMoreStuff(arg3, arg4, function(err, result) {
        doEvenMoreStuff(arg5, arg6, function(err, result) {
            omgHowDidIGetHere();
        });
    });
});

La guida di stile ufficiale dice di mettere ogni callback in una funzione separata, ma ciò sembra eccessivamente restrittivo sull'uso delle chiusure, e rendere un singolo oggetto dichiarato nel livello superiore disponibile diversi livelli verso il basso, poiché l'oggetto deve essere passato attraverso tutti i richiamate intermedie.

Va bene usare l'ambito delle funzioni per aiutare qui? Metti tutte le funzioni di callback che hanno bisogno di accedere a un oggetto global-ish all'interno di una funzione che dichiara quell'oggetto, quindi va in chiusura?

function topLevelFunction(globalishObject, callback) {

    function doMoreStuffImpl(err, result) {
        doMoreStuff(arg5, arg6, function(err, result) {
            callback(null, globalishObject);
        });
    }

    doStuff(arg1, arg2, doMoreStuffImpl);
}

e così via per molti altri livelli ...

Oppure ci sono framework ecc per aiutare a ridurre i livelli di rientro senza dichiarare una funzione con nome per ogni singolo callback? Come gestisci la piramide del callback?


2
Nota un ulteriore problema con le chiusure: nella chiusura JS acquisisce l'intero contesto padre (in altre lingue cattura solo le variabili utilizzate o quelle richieste dall'utente in modo specifico) causando delle belle perdite di memoria se la gerarchia di callback è abbastanza profonda e se, ad esempio, la richiamata viene mantenuta da qualche parte.
Eugene,

Risposte:


7

Le promesse forniscono una netta separazione delle preoccupazioni tra il comportamento asincrono e l'interfaccia in modo che le funzioni asincrone possano essere richiamate senza richiamate e l'interazione di richiamata possa essere effettuata sull'interfaccia generica della promessa.

Esistono molteplici implementazioni di "promesse":


Ad esempio, è possibile riscrivere questi callback nidificati

http.get(url.parse("http://test.com/"), function(res) {
    console.log(res.statusCode);
    http.get(url.parse(res.headers["location"]), function(res) {
        console.log(res.statusCode);
    });
});

piace

httpGet(url.parse("http://test.com/")).then(function (res) {
    console.log(res.statusCode);
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);
});

Invece di richiamare in richiamata, a(b(c()))si collega ".then" a().then(b()).then(c()).


Un'introduzione qui: http://howtonode.org/promises


ti dispiacerebbe spiegare di più su ciò che fanno queste risorse e perché le consigli a rispondere alla domanda posta? Le "risposte solo link" non sono del tutto benvenute allo Stack Exchange
moscerino

1
Ok scusa. Ho aggiunto un esempio e ulteriori informazioni.
Fabien Sa

3

In alternativa alle promesse, dovresti dare un'occhiata alla yieldparola chiave in combinazione con le funzioni del generatore che saranno introdotte in EcmaScript 6. Entrambe sono disponibili oggi nelle build Node.js 0.11.x, ma è necessario specificare ulteriormente il --harmonyflag quando si esegue Node .js:

$ node --harmony app.js

L'uso di questi costrutti e una libreria come la co di TJ Holowaychuk ti permettono di scrivere codice asincrono in uno stile che assomiglia a codice sincrono, anche se funziona ancora in modo asincrono. Fondamentalmente, queste cose insieme implementano il supporto di co-routine per Node.js.

Fondamentalmente, quello che devi fare è scrivere una funzione del generatore per il codice che esegue roba asincrona, chiamare la roba asincrona lì dentro, ma prefissarla con la yieldparola chiave. Quindi, alla fine il tuo codice assomiglia a:

var run = function * () {
  var data = yield doSomethingAsync();
  console.log(data);
};

Per eseguire questa funzione del generatore è necessaria una libreria come la co precedentemente citata. La chiamata quindi appare come:

co(run);

Oppure, per dirla in linea:

co(function * () {
  // ...
});

Si noti che dalle funzioni del generatore è possibile chiamare altre funzioni del generatore, tutto ciò che è necessario fare è prefissarle yieldnuovamente.

Per un'introduzione a questo argomento, cerca in Google termini come yield generators es6 async nodejse dovresti trovare tonnellate di informazioni. Ci vuole un po 'di tempo per abituarsi, ma una volta capito, non vorrai più tornare indietro.

Si noti che ciò non fornisce solo una sintassi migliore per chiamare le funzioni, ma consente anche di utilizzare le solite robe logiche del flusso di controllo (sincrono), come forloop o try/ catch. Non dovrai più fare casino con molti callback e tutte queste cose.

Buona fortuna e buon divertimento :-)!


0

Ora hai il pacchetto asyncawait , con una sintassi molto stretta a quello che dovrebbe essere il futuro supporto nativo di await& asyncin Node.

Fondamentalmente ti consente di scrivere codice asincrono sembrando sincrono , riducendo drasticamente i livelli di LOC e rientro, con il compromesso di una leggera perdita di prestazioni (i numeri del proprietario del pacchetto hanno una velocità del 79% rispetto ai callback grezzi), si spera ridotto quando sarà disponibile il supporto nativo.

Ancora una buona scelta per uscire dall'inferno callback / piramide del destino incubo IMO, quando le prestazioni non sono la preoccupazione principale e dove lo stile di scrittura sincrona si adatta meglio alle esigenze del tuo progetto.

Esempio di base dal pacchetto di documenti:

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.