Come evitare l'annidamento lungo di funzioni asincrone in Node.js


158

Voglio creare una pagina che visualizza alcuni dati da un DB, quindi ho creato alcune funzioni che ottengono quei dati dal mio DB. Sono solo un principiante in Node.js, per quanto ho capito, se voglio usarli tutti in una singola pagina (risposta HTTP) dovrei nidificarli tutti:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

Se ci sono molte funzioni del genere, l'annidamento diventa un problema .

c'è un modo per evitarlo? Immagino abbia a che fare con il modo in cui si combinano più funzioni asincrone, che sembra essere qualcosa di fondamentale.


12
Quindi quando hai 10 funzioni asincrone, hai 10 livelli di rientro?
Kay Pale,

Questo link può essere d'aiuto. stackoverflow.com/a/4631909/290340
Evan Plaice

1
Un altro problema: inserire un'altra funzione tra getSomeDatee getSomeOtherDatefinire nel cambiare il rientro di molte righe che rende più difficile la lettura della cronologia git ( git blameè anche inutile dopo questo), e probabilmente si creano errori quando si esegue questa operazione manualmente
Daniel Alder,

Risposte:


73

Osservazione interessante Si noti che in JavaScript è possibile sostituire normalmente le funzioni di callback anonime incorporate con variabili di funzioni denominate.

Il seguente:

http.createServer(function (req, res) {
   // inline callback function ...

   getSomeData(client, function (someData) {
      // another inline callback function ...

      getMoreData(client, function(moreData) {
         // one more inline callback function ...
      });
   });

   // etc ...
});

Potrebbe essere riscritto per assomigliare a questo:

var moreDataParser = function (moreData) {
   // date parsing logic
};

var someDataParser = function (someData) {
   // some data parsing logic

   getMoreData(client, moreDataParser);
};

var createServerCallback = function (req, res) {
   // create server logic

   getSomeData(client, someDataParser);

   // etc ...
};

http.createServer(createServerCallback);

Tuttavia, a meno che non si preveda di riutilizzare la logica di callback in altri luoghi, è spesso molto più semplice leggere funzioni anonime incorporate, come nell'esempio. Ti risparmierà anche di dover trovare un nome per tutti i callback.

Inoltre, come notato da @pst in un commento qui sotto, se si accede alle variabili di chiusura all'interno delle funzioni interne, quanto sopra non sarebbe una traduzione semplice. In tali casi, l'utilizzo di funzioni anonime incorporate è ancora più preferibile.


26
Tuttavia, (e davvero solo per capire il compromesso) quando non nidificati, alcune semantiche di chiusura sulle variabili possono essere perse, quindi non è una traduzione diretta. Nell'esempio precedente, l'accesso a "res" in getMoreDataviene perso.

2
Penso che la tua soluzione sia rotta: in someDataParserrealtà analizza TUTTI i dati, poiché chiama anche getMoreData. In tal senso, il nome della funzione non è corretto e diventa evidente che non abbiamo effettivamente rimosso il problema di nidificazione.
Konstantin Schubert,

63

Kay, usa semplicemente uno di questi moduli.

Girerà questo:

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', 'bobvance@potato.egg', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

In questo:

flow.exec(
    function() {
        dbGet('userIdOf:bobvance', this);

    },function(userId) {
        dbSet('user:' + userId + ':email', 'bobvance@potato.egg', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

    },function() {
        okWeAreDone()
    }
);

9
Ho dato una rapida occhiata a flow-js, step e async e sembra che abbiano a che fare solo con l'ordine di esecuzione della funzione. Nel mio caso c'è accesso alle variabili di chiusura inline in ogni rientro. Quindi, ad esempio, le funzioni funzionano in questo modo: ottieni HTTP req / res, ottieni userid da DB per cookie, ottieni email per l'id utente successivo, ottieni più dati per l'e-mail successiva, ..., ottieni X per Y successiva, ... Se non sbaglio, questi framework assicurano solo che le funzioni asincrone saranno eseguite nell'ordine corretto, ma in ogni corpo di funzione non c'è modo di ottenere la variabile fornita naturalmente dalle chiusure (?) Grazie :)
Kay Pale

9
In termini di classificazione di queste biblioteche, ho verificato il numero di "stelle" su ciascuna su Github. async ha il massimo con circa 3000, Step è il prossimo con circa 1000, gli altri sono significativamente inferiori. Certo, non fanno tutti la stessa cosa :-)
kgilpin

3
@KayPale Tendo a utilizzare async.waterfall e a volte avrò le mie funzioni per ogni fase / passaggio che passerà lungo ciò di cui ha bisogno il passaggio successivo o definire le variabili prima della chiamata async.METHOD in modo che sia disponibile downline. Utilizzerò anche METHODNAME.bind (...) per le mie chiamate asincrone. *, Che funziona anche abbastanza bene.
Tracker1

Una domanda veloce: nel tuo elenco di moduli, gli ultimi due sono uguali? Vale a dire "async.js" e "async"
dari0h

18

Per la maggior parte, concordo con Daniel Vassallo. Se riesci a suddividere una funzione complicata e profondamente annidata in funzioni separate, allora di solito è una buona idea. Per i momenti in cui ha senso farlo all'interno di una singola funzione, è possibile utilizzare una delle tante librerie asincrone node.js disponibili. Le persone hanno escogitato molti modi diversi per affrontare questo problema, quindi dai un'occhiata alla pagina dei moduli node.js e vedi cosa ne pensi.

Ho scritto un modulo per questo, chiamato async.js . Utilizzando questo, l'esempio sopra potrebbe essere aggiornato per:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  async.series({
    someData: async.apply(getSomeDate, client),
    someOtherData: async.apply(getSomeOtherDate, client),
    moreData: async.apply(getMoreData, client)
  },
  function (err, results) {
    var html = "<h1>Demo page</h1>";
    html += "<p>" + results.someData + "</p>";
    html += "<p>" + results.someOtherData + "</p>";
    html += "<p>" + results.moreData + "</p>";
    res.write(html);
    res.end();
  });
});

Una cosa bella di questo approccio è che puoi cambiare rapidamente il tuo codice per recuperare i dati in parallelo cambiando la funzione 'serie' in 'parallelo'. Inoltre, async.js funzionerà anche all'interno del browser, quindi puoi usare gli stessi metodi che faresti in node.js se dovessi incontrare qualche codice asincrono.

Spero sia utile!


Ciao Caolan e grazie per la risposta! Nel mio caso c'è accesso alle variabili di chiusura inline in ogni rientro. Quindi, ad esempio, le funzioni funzionano in questo modo: ottieni HTTP req / res, ottieni userid da DB per cookie, ottieni email per l'id utente successivo, ottieni più dati per l'e-mail successiva, ..., ottieni X per Y successiva, ... Se non sbaglio, il codice che suggerisci assicura solo che le funzioni asincrone saranno eseguite nell'ordine corretto, ma in ogni corpo di funzione non c'è modo di ottenere la variabile fornita naturalmente dalle chiusure nel mio codice originale. È così?
Kay Pale,

3
Quello che stai cercando di ottenere è architettonicamente chiamato pipeline di dati. È possibile utilizzare la cascata asincrona per tali casi.
Rudolf Meijering,

18

È possibile utilizzare questo trucco con un array anziché con funzioni nidificate o un modulo.

Molto più facile per gli occhi.

var fs = require("fs");
var chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step3");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step4");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step5");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("done");
    },
];
chain.shift()();

È possibile estendere il linguaggio per processi paralleli o anche catene parallele di processi:

var fs = require("fs");
var fork1 = 2, fork2 = 2, chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        var next = chain.shift();
        fs.stat("f2a.js",next);
        fs.stat("f2b.js",next);
    },
    function(err, stats) {
        if ( --fork1 )
            return;
        console.log("step3");
        var next = chain.shift();

        var chain1 = [
            function() { 
                console.log("step4aa");
                fs.stat("f1.js",chain1.shift());
            },
            function(err, stats) { 
                console.log("step4ab");
                fs.stat("f1ab.js",next);
            },
        ];
        chain1.shift()();

        var chain2 = [
            function() { 
                console.log("step4ba");
                fs.stat("f1.js",chain2.shift());
            },
            function(err, stats) { 
                console.log("step4bb");
                fs.stat("f1ab.js",next);
            },
        ];
        chain2.shift()();
    },
    function(err, stats) {
        if ( --fork2 )
            return;
        console.log("done");
    },
];
chain.shift()();

15

Mi piace molto async.js per questo scopo.

Il problema è risolto dal comando waterfall:

cascata (attività, [richiamata])

Esegue una serie di funzioni in serie, ognuna delle quali passa i risultati alla successiva nella matrice. Tuttavia, se una delle funzioni trasmette un errore al callback, la funzione successiva non viene eseguita e il callback principale viene immediatamente chiamato con l'errore.

argomenti

attività - Una matrice di funzioni da eseguire, a ciascuna funzione viene passato un callback (err, result1, result2, ...) che deve chiamare al completamento. Il primo argomento è un errore (che può essere nullo) e qualsiasi ulteriore argomento verrà passato come argomento per l'attività successiva. callback (err, [risultati]) - Un callback opzionale da eseguire una volta completate tutte le funzioni. In questo modo verranno passati i risultati del callback dell'ultima attività.

Esempio

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'    
});

Per quanto riguarda le variabili req, res, saranno condivise nello stesso ambito della funzione (req, res) {} che racchiudeva l'intera chiamata async.waterfall.

Non solo, async è molto pulito. Ciò che intendo è che cambio molti casi come questo:

function(o,cb){
    function2(o,function(err, resp){
        cb(err,resp);
    })
}

Per prima cosa:

function(o,cb){
    function2(o,cb);
}

Quindi a questo:

function2(o,cb);

Quindi a questo:

async.waterfall([function2,function3,function4],optionalcb)

Inoltre, consente a molte funzioni predefinite preparate per l'asincronismo di essere chiamate da util.js molto velocemente. Basta incatenare ciò che si desidera fare, assicurarsi che o, cb sia gestito universalmente. Questo accelera molto l'intero processo di codifica.


11

Ciò di cui hai bisogno è un po 'di zucchero sintattico. Dai un'occhiata a questo:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = ["<h1>Demo page</h1>"];
  var pushHTML = html.push.bind(html);

  Queue.push( getSomeData.partial(client, pushHTML) );
  Queue.push( getSomeOtherData.partial(client, pushHTML) );
  Queue.push( getMoreData.partial(client, pushHTML) );
  Queue.push( function() {
    res.write(html.join(''));
    res.end();
  });
  Queue.execute();
}); 

Abbastanza pulito , no? Potresti notare che html è diventato un array. Ciò è in parte dovuto al fatto che le stringhe sono immutabili, quindi è meglio bufferizzare l'output in un array, piuttosto che scartare stringhe sempre più grandi. L'altro motivo è a causa di un'altra bella sintassi con bind.

Queuenell'esempio è davvero solo un esempio e insieme partialpuò essere implementato come segue

// Functional programming for the rescue
Function.prototype.partial = function() {
  var fun = this,
      preArgs = Array.prototype.slice.call(arguments);
  return function() {
    fun.apply(null, preArgs.concat.apply(preArgs, arguments));
  };
};

Queue = [];
Queue.execute = function () {
  if (Queue.length) {
    Queue.shift()(Queue.execute);
  }
};

1
Queue.execute () eseguirà semplicemente i parziali uno dopo l'altro, senza attendere i risultati delle chiamate asincrone.
ngn

Grazie, grazie. Ho aggiornato la risposta. Ecco un test: jsbin.com/ebobo5/edit (con una lastfunzione opzionale )
gblazex

Ciao galambalazs e grazie per la risposta! Nel mio caso c'è accesso alle variabili di chiusura incorporate in ogni rientro. Quindi, ad esempio, le funzioni funzionano in questo modo: ottieni HTTP req / res, ottieni userid da DB per cookie, ottieni email per l'id utente successivo, ottieni più dati per l'e-mail successiva, ..., ottieni X per Y successiva, ... Se non sbaglio, il codice che suggerisci assicura solo che le funzioni asincrone saranno eseguite nell'ordine corretto, ma in ogni corpo di funzione non c'è modo di ottenere la variabile fornita naturalmente dalle chiusure nel mio codice originale. È così?
Kay Pale,

1
Bene, perdi sicuramente la chiusura in tutte le risposte. Quello che puoi fare è creare un oggetto nell'ambito globale per i dati condivisi . Quindi, ad esempio, la tua prima funzione viene aggiunta obj.emaile la successiva viene utilizzata, obj.emailquindi la elimina (o semplicemente assegna null).
gblazex,

7

Sono innamorata di Async.js da quando l'ho trovata. Ha una async.seriesfunzione che puoi usare per evitare un lungo annidamento.

Documentazione:-


serie (attività, [richiamata])

Esegui una serie di funzioni in serie, ognuna in esecuzione una volta completata la funzione precedente. [...]

argomenti

tasks- Una serie di funzioni da eseguire, a ciascuna funzione viene passato un callback che deve chiamare al completamento. callback(err, [results])- Un callback opzionale da eseguire una volta completate tutte le funzioni. Questa funzione ottiene un array di tutti gli argomenti passati ai callback usati nell'array.


Ecco come possiamo applicarlo al tuo codice di esempio: -

http.createServer(function (req, res) {

    res.writeHead(200, {'Content-Type': 'text/html'});

    var html = "<h1>Demo page</h1>";

    async.series([
        function (callback) {
            getSomeData(client, function (someData) { 
                html += "<p>"+ someData +"</p>";

                callback();
            });
        },

        function (callback) {
            getSomeOtherData(client, function (someOtherData) { 
                html += "<p>"+ someOtherData +"</p>";

                callback(); 
            });
        },

        funciton (callback) {
            getMoreData(client, function (moreData) {
                html += "<p>"+ moreData +"</p>";

                callback();
            });
        }
    ], function () {
        res.write(html);
        res.end();
    });
});

6

Lo zucchero sintattico più semplice che ho visto è il nodo-promessa.

npm install node-promise || git clone https://github.com/kriszyp/node-promise

Usando questo è possibile concatenare metodi asincroni come:

firstMethod().then(secondMethod).then(thirdMethod);

Il valore di ritorno di ciascuno è disponibile come argomento nel prossimo.


3

Quello che hai fatto è prendere un modello asincrono e applicarlo a 3 funzioni chiamate in sequenza, ognuna in attesa del completamento della precedente prima di iniziare, ovvero le hai rese sincrone . Il punto sulla programmazione asincrona è che puoi avere diverse funzioni tutte in esecuzione contemporaneamente e non dover aspettare che ognuna venga completata.

se getSomeDate () non fornisce nulla a getSomeOtherDate (), che non fornisce nulla a getMoreData (), allora perché non chiamarli in modo asincrono come js consente o se sono interdipendenti (e non asincroni), scrivili come singola funzione?

Non è necessario utilizzare l'annidamento per controllare il flusso - ad esempio, far terminare ciascuna funzione chiamando una funzione comune che determina quando tutti e 3 sono stati completati e quindi invia la risposta.


2

Supponiamo che tu possa fare questo:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var html = "<h1>Demo page</h1>";
    chain([
        function (next) {
            getSomeDate(client, next);
        },
        function (next, someData) {
            html += "<p>"+ someData +"</p>";
            getSomeOtherDate(client, next);
        },
        function (next, someOtherData) {
            html += "<p>"+ someOtherData +"</p>";
            getMoreData(client, next);
        },
        function (next, moreData) {
            html += "<p>"+ moreData +"</p>";
            res.write(html);
            res.end();
        }
    ]);
});

Devi solo implementare chain () in modo che applichi parzialmente ciascuna funzione a quella successiva e invochi immediatamente solo la prima funzione:

function chain(fs) {
    var f = function () {};
    for (var i = fs.length - 1; i >= 0; i--) {
        f = fs[i].partial(f);
    }
    f();
}

Ciao ngn e grazie per la risposta! Nel mio caso c'è accesso alle variabili di chiusura inline in ogni rientro. Quindi, ad esempio, le funzioni funzionano in questo modo: ottieni HTTP req / res, ottieni userid da DB per cookie, ottieni email per l'id utente successivo, ottieni più dati per l'e-mail successiva, ..., ottieni X per Y successiva, ... Se non sbaglio, il codice che suggerisci assicura solo che le funzioni asincrone saranno eseguite nell'ordine corretto, ma in ogni corpo di funzione non c'è modo di ottenere la variabile fornita naturalmente dalle chiusure nel mio codice originale. È così?
Kay Pale,

2

l'inferno di callback può essere facilmente evitato in javascript puro con chiusura. la soluzione seguente presuppone che tutti i callback seguano la firma della funzione (errore, dati).

http.createServer(function (req, res) {
  var modeNext, onNext;

  // closure variable to keep track of next-callback-state
  modeNext = 0;

  // next-callback-handler
  onNext = function (error, data) {
    if (error) {
      modeNext = Infinity;
    } else {
      modeNext += 1;
    }
    switch (modeNext) {

    case 0:
      res.writeHead(200, {'Content-Type': 'text/html'});
      var html = "<h1>Demo page</h1>";
      getSomeDate(client, onNext);
      break;

    // handle someData
    case 1:
        html += "<p>"+ data +"</p>";
        getSomeOtherDate(client, onNext);
        break;

    // handle someOtherData
    case 2:
      html += "<p>"+ data +"</p>";
      getMoreData(client, onNext);
      break;

    // handle moreData
    case 3:
      html += "<p>"+ data +"</p>";
      res.write(html);
      res.end();
      break;

    // general catch-all error-handler
    default:
      res.statusCode = 500;
      res.end(error.message + '\n' + error.stack);
    }
  };
  onNext();
});

1

Di recente ho creato un'astrazione più semplice chiamata wait.for per chiamare le funzioni asincrone in modalità di sincronizzazione (basata su Fibre). È in una fase iniziale ma funziona. È a:

https://github.com/luciotato/waitfor

Usando wait.for , puoi chiamare qualsiasi funzione asincrona nodejs standard, come se fosse una funzione di sincronizzazione.

usando wait.for il tuo codice potrebbe essere:

var http=require('http');
var wait=require('wait.for');

http.createServer(function(req, res) {
  wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning
}).listen(8080);


//in a fiber
function handleRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  var someData = wait.for(getSomeDate,client);
  html += "<p>"+ someData +"</p>";
  var someOtherData = wait.for(getSomeOtherDate,client);
  html += "<p>"+ someOtherData +"</p>";
  var moreData = wait.for(getMoreData,client);
  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
};

... o se vuoi essere meno prolisso (e anche aggiungere errori di cattura)

//in a fiber
function handleRequest(req, res) {
  try {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(
    "<h1>Demo page</h1>" 
    + "<p>"+ wait.for(getSomeDate,client) +"</p>"
    + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>"
    + "<p>"+ wait.for(getMoreData,client) +"</p>"
    );
    res.end();
  }
  catch(err) {
   res.end('error '+e.message); 
  }

};

In tutti i casi, getSomeDate , getSomeOtherDate e getMoreData dovrebbero essere funzioni asincrone standard con l'ultimo parametro una funzione di callback (err, data)

come in:

function getMoreData(client, callback){
  db.execute('select moredata from thedata where client_id=?',[client.id],
       ,function(err,data){
          if (err) callback(err);
          callback (null,data);
        });
}

1

Per risolvere questo problema ho scritto nodent ( https://npmjs.org/package/nodent ) che pre-elabora invisibilmente il tuo JS. Il tuo codice di esempio diventerebbe (asincrono, davvero - leggi i documenti).

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  someData <<= getSomeDate(client) ;

  html += "<p>"+ someData +"</p>";
  someOtherData <<= getSomeOtherDate(client) ;

  html += "<p>"+ someOtherData +"</p>";
  moreData <<= getMoreData(client) ;

  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
});

Chiaramente, ci sono molte altre soluzioni, ma la pre-elaborazione ha il vantaggio di avere un sovraccarico di runtime minimo o nullo e grazie al supporto della mappa di origine è anche facile eseguire il debug.


0

Ho avuto lo stesso problema. Ho visto le principali librerie per eseguire le funzioni asincrone del nodo e presentano un concatenamento non naturale (è necessario utilizzare tre o più metodi conf. Ecc.) Per costruire il codice.

Ho trascorso alcune settimane a sviluppare una soluzione per essere semplice e di facile lettura. Per favore, prova EnqJS . Tutte le opinioni saranno apprezzate.

Invece di:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

con EnqJS:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";

  enq(function(){
    var self=this;
    getSomeDate(client, function(someData){
      html += "<p>"+ someData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getSomeOtherDate(client, function(someOtherData){ 
      html += "<p>"+ someOtherData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getMoreData(client, function(moreData) {
      html += "<p>"+ moreData +"</p>";
      self.return();
      res.write(html);
      res.end();
    });
  });
});

Si noti che il codice sembra essere più grande di prima. Ma non è nidificato come prima. Per apparire più naturali, le catene sono chiamate immediatamente:

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...)

E dire che è tornato, all'interno della funzione che chiamiamo:

this.return(response)

0

Lo faccio in un modo piuttosto primitivo ma efficace. Ad esempio, devo ottenere un modello con i suoi genitori e figli e diciamo che devo fare domande separate per loro:

var getWithParents = function(id, next) {
  var getChildren = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      },
      getParents = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      }
      getModel = function(id, next) {
        /*... code ... */
        if (model) {
          // return next callbacl
          return next.pop()(model, next);
        } else {
          // return last callback
          return next.shift()(null, next);
        }
      }

  return getModel(id, [getParents, getChildren, next]);
}

0

Usa Fibre https://github.com/laverdet/node-fibers rende il codice asincrono simile a sincrono (senza blocco)

Personalmente uso questo piccolo wrapper http://alexeypetrushin.github.com/synchronize Esempio di codice dal mio progetto (ogni metodo è in realtà asincrono, funzionante con file asincrono IO) Ho persino paura di immaginare che casino sarebbe con callback o librerie di supporto per flusso di controllo asincrono.

_update: (version, changesBasePath, changes, oldSite) ->
  @log 'updating...'
  @_updateIndex version, changes
  @_updateFiles version, changesBasePath, changes
  @_updateFilesIndexes version, changes
  configChanged = @_updateConfig version, changes
  @_updateModules version, changes, oldSite, configChanged
  @_saveIndex version
  @log "updated to #{version} version"

0

Task.js ti offre questo:

spawn(function*() {
    try {
        var [foo, bar] = yield join(read("foo.json"),
                                    read("bar.json")).timeout(1000);
        render(foo);
        render(bar);
    } catch (e) {
        console.log("read failed: " + e);
    }
});

Invece di questo:

var foo, bar;
var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000);

var xhr1 = makeXHR("foo.json",
                   function(txt) { foo = txt; success() },
                   function(err) { failure() });
var xhr2 = makeXHR("bar.json",
                   function(txt) { bar = txt; success() },
                   function(e) { failure(e) });

function success() {
    if (typeof foo === "string" && typeof bar === "string") {
        cancelTimeout(tid);
        xhr1 = xhr2 = null;
        render(foo);
        render(bar);
    }
}

function failure(e) {
    xhr1 && xhr1.abort();
    xhr1 = null;
    xhr2 && xhr2.abort();
    xhr2 = null;
    console.log("read failed: " + e);
}

0

Dopo che gli altri hanno risposto, hai dichiarato che il tuo problema erano variabili locali. Sembra un modo semplice per farlo è scrivere una funzione esterna per contenere quelle variabili locali, quindi utilizzare un gruppo di funzioni interne denominate e accedervi per nome. In questo modo, ne anniderai solo due in profondità, indipendentemente da quante funzioni devi mettere insieme.

Ecco il tentativo del mio principiante di utilizzare il mysqlmodulo Node.js con annidamento:

function with_connection(sql, bindings, cb) {
    pool.getConnection(function(err, conn) {
        if (err) {
            console.log("Error in with_connection (getConnection): " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, function(err, results) {
            if (err) {
                console.log("Error in with_connection (query): " + JSON.stringify(err));
                cb(true);
                return;
            }
            console.log("with_connection results: " + JSON.stringify(results));
            cb(false, results);
        });
    });
}

Di seguito è una riscrittura che utilizza le funzioni interne denominate. La funzione esterna with_connectionpuò essere utilizzata anche come supporto per le variabili locali. (Qui, ho i parametri sql, bindings, cbche agiscono in modo simile, ma si può solo definire alcune variabili locali addizionali with_connection.)

function with_connection(sql, bindings, cb) {

    function getConnectionCb(err, conn) {
        if (err) {
            console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, queryCb);
    }

    function queryCb(err, results) {
        if (err) {
            console.log("Error in with_connection/queryCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        cb(false, results);
    }

    pool.getConnection(getConnectionCb);
}

Pensavo che forse sarebbe stato possibile creare un oggetto con variabili di istanza e utilizzare queste variabili di istanza in sostituzione delle variabili locali. Ma ora trovo che l'approccio sopra usando funzioni nidificate e variabili locali è più semplice e più facile da capire. Ci vuole del tempo per disimparare OO, sembra :-)

Quindi ecco la mia versione precedente con un oggetto e variabili di istanza.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); });
}
DbConnection.prototype.query = function(err, results) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        self.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    self.cb(false, results);
}

function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); });
}

Si scopre che bindpuò essere usato con qualche vantaggio. Mi permette di sbarazzarmi delle funzioni anonime un po 'brutte che ho creato che non hanno fatto molto, tranne che per inoltrarsi a una chiamata di metodo. Non ho potuto passare direttamente il metodo perché sarebbe stato coinvolto con il valore errato di this. Ma con bind, posso specificare il valore thische desidero.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var f = this.query.bind(this);
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, f);
}
DbConnection.prototype.query = function(err, results) {
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    this.cb(false, results);
}

// Get a connection from the pool, execute `sql` in it
// with the given `bindings`.  Invoke `cb(true)` on error,
// invoke `cb(false, results)` on success.  Here,
// `results` is an array of results from the query.
function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    var f = dbc.getConnection.bind(dbc);
    pool.getConnection(f);
}

Ovviamente, niente di tutto questo è corretto JS con la codifica Node.js - ho appena trascorso un paio d'ore su di esso. Ma forse con un po 'di lucidatura questa tecnica può aiutare?





0

Usando il filo il tuo codice sarebbe simile al seguente:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});

    var l = new Wire();

    getSomeDate(client, l.branch('someData'));
    getSomeOtherDate(client, l.branch('someOtherData'));
    getMoreData(client, l.branch('moreData'));

    l.success(function(r) {
        res.write("<h1>Demo page</h1>"+
            "<p>"+ r['someData'] +"</p>"+
            "<p>"+ r['someOtherData'] +"</p>"+
            "<p>"+ r['moreData'] +"</p>");
        res.end();
    });
});

0

per la tua conoscenza considera Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase

    const jj = require ('jazz.js');

    // stack ultra-compatibile
    jj.script ([
        a => ProcessTaskOneCallbackAtEnd (a),
        b => ProcessTaskTwoCallbackAtEnd (b),
        c => ProcessTaskThreeCallbackAtEnd (c),
        d => ProcessTaskFourCallbackAtEnd (d),
        e => ProcessTaskFiveCallbackAtEnd (e),
    ]);

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.