2009-11-27 17 views
19

In Nodejs, non ci sono praticamente operazioni di I/O di blocco. Ciò significa che quasi tutti i codici IO nodejs coinvolgono molte callback. Questo vale per la lettura e la scrittura da/per database, file, processi, ecc Un tipico esempio di questo è la seguente:Gestione di molte ricorsioni di callback in Nodejs

var useFile = function(filename,callback){ 
    posix.stat(filename).addCallback(function (stats) { 
     posix.open(filename, process.O_RDONLY, 0666).addCallback(function (fd) { 
      posix.read(fd, stats.size, 0).addCallback(function(contents){ 
       callback(contents); 
      }); 
     }); 
    }); 
}; 

... 

useFile("test.data",function(data){ 
    // use data.. 
}); 

Sto anticipando la scrittura di codice che renderà molti operazioni di IO, quindi mi aspetto per scrivere molti callbacks. Sono abbastanza a mio agio nell'usare i callback, ma sono preoccupato per tutte le ricorsioni. Sono in pericolo di incappare in troppe ricorsioni e saltare da una pila da qualche parte? Se faccio migliaia di singole scritture sul mio negozio di valori-chiave con migliaia di callback, il mio programma finirà per bloccarsi?

Sto fraintendendo o sottostimando l'impatto? In caso contrario, c'è un modo per aggirare questo mentre si utilizza ancora lo stile di codifica del callback di Nodejs?

+0

L'argomento è vecchio ma controlla asincrono: https://github.com/caolan/async – fider

risposta

24

Nessuno del codice che si sta visualizzando utilizza la ricorsione. Quando chiami useFile chiama posix.stat(), che restituisce e useFile termina come è stato eseguito fino al completamento. In un secondo momento, quando la chiamata a posix.stat() ha completato all'interno del sistema sottostante e i risultati sono disponibili, la funzione di callback aggiunta verrà eseguita. Ciò chiama posix.open() e quindi termina come è stato eseguito fino al completamento. Una volta che il file è stato aperto, la funzione di callback per verrà eseguita, chiamando posix.read() e terminerà così come è stata completata. Infine, quando sono disponibili i risultati della lettura, verrà eseguita la funzione più interna.

Il punto importante è che ogni funzione viene eseguita fino al completamento, poiché le chiamate alle funzioni posix.*() non sono bloccanti: ovvero, restituiscono immediatamente, avendo causato l'avvio di alcune magie nel sistema sottostante. Quindi ciascuna funzione termina e in seguito un evento farà eseguire la funzione successiva; ma in nessun punto c'è alcuna ricorsione.

La struttura nidificata del codice può dare l'impressione che le cose all'interno dovranno finire prima che la roba esterna possa arrivare al proprio punto finale. Ma in questo stile di programmazione asincrona basata sugli eventi è più logico vedere l'annidamento in termini di più profondo => accade più tardi di.

MODIFICA: provare ad aggiungere alcune dichiarazioni di registrazione immediatamente prima della fine di ogni funzione annidata; questo aiuterà ad illustrare che l'ordine in cui vengono completati è dall'interno verso l'esterno.

+0

Grazie per aver chiarito i miei pensieri confusi su questo argomento. Devo ancora lavorare con nodejs un po 'di più e fare un po' di test (come il logging che suggerisci) per convincermi che non sto costruendo una montagna di scope o qualcosa del genere, ma questo è un passo avanti direzione. Grazie ancora – Maciek

0

Come con qualsiasi JavaScript, è possibile effettuare chiamate ricorsive con Node.js. Se ti imbatti in problemi di profondità di ricorsione (come sottolinea NickFitz, non sembri essere in pericolo), puoi spesso riscrivere il tuo codice per utilizzare invece un timer di intervallo.

1

Le tue cose vanno bene. Faccio chiamate ricorsive in veloce da seguire reindirizzamenti HTTP, ma cosa stai facendo è "trasversale" e non la ricorsione

3

Lo stesso esempio, con output di debug aggiunto (vedi sotto per l'uscita):

usefile.JS:

var sys = require("sys"), 
    posix = require("posix"); 

var useFile = function(filename,callback){ 
    posix.stat(filename).addCallback(function (stats) { 
     posix.open(filename, process.O_RDONLY, 0666).addCallback(function (fd) { 
      posix.read(fd, stats.size, 0).addCallback(function(contents){ 
       callback(contents); 
       sys.debug("useFile callback returned"); 
      }); 
      sys.debug("read returned"); 
     }); 
     sys.debug("open returned"); 
    }); 
    sys.debug("stat returned"); 
}; 

useFile("usefile.js",function(){}); 

uscita:

DEBUG: stat returned 
DEBUG: open returned 
DEBUG: read returned 
DEBUG: useFile callback returned 
3

Si può provare

http://github.com/creationix/do

o rotolare il proprio come ho fatto io. Non importa la gestione per ora l'errore mancante (basta ignorare quello);)

var sys = require('sys'); 

var Simplifier = exports.Simplifier = function() {} 

Simplifier.prototype.execute = function(context, functions, finalFunction) { 
    this.functions = functions; 
    this.results = {}; 
    this.finalFunction = finalFunction; 
    this.totalNumberOfCallbacks = 0 
    this.context = context; 
    var self = this; 

    functions.forEach(function(f) { 
    f(function() { 
     self.totalNumberOfCallbacks = self.totalNumberOfCallbacks + 1; 
     self.results[f] = Array.prototype.slice.call(arguments, 0);  
     if(self.totalNumberOfCallbacks >= self.functions.length) { 
     // Order the results by the calling order of the functions 
     var finalResults = []; 
     self.functions.forEach(function(f) { 
      finalResults.push(self.results[f][0]); 
     }) 
     // Call the final function passing back all the collected results in the right order 
     finalFunction.apply(self.context, finalResults); 
     } 
    }); 
    }); 
} 

E un semplice esempio di usarlo

// Execute 
new simplifier.Simplifier().execute(
    // Context of execution 
    self, 
    // Array of processes to execute before doing final handling 
    [function(callback) { 
     db.collection('githubusers', function(err, collection) { 
     collection.find({}, {limit:30}, function(err, cursor) { 
      cursor.toArray(function(err, users) { callback(users); }) 
     }); 
     });  
    }, 

    function(callback) { 
     db.collection('githubprojects', function(err, collection) { 
     collection.find({}, {limit:45, sort:[['watchers', -1]]}, function(err, cursor) { 
      cursor.toArray(function(err, projects) { callback(projects); }) 
     }); 
     });    
    } 
    ], 
    // Handle the final result 
    function(users, projects) { 
    // Do something when ready 
    } 
); 
1

prendere anche uno sguardo a 'step' (http://github.com/creationix/step) o 'flow-js 'su github. Ciò consente di scrivere flussi di callback in uno stile più naturale. Ciò renderà anche chiaro che non si sta verificando alcuna ricorsione.