2011-03-02 15 views
92

Supponiamo di dover eseguire alcune operazioni che dipendono da alcuni file temporanei. Dal stiamo parlando di nodo qui, quelle operazioni sono ovviamente asincrone. Qual è il modo idiomatico di aspettare che tutte le operazioni finiscano per poter sapere quando il file temporaneo può essere cancellato?Modo idiomatico per attendere più callback in Node.js

Ecco po 'di codice che mostra quello che voglio fare:

do_something(tmp_file_name, function(err) {}); 
do_something_other(tmp_file_name, function(err) {}); 
fs.unlink(tmp_file_name); 

Ma se scrivo in questo modo, la terza chiamata può essere eseguito prima che il primo due avere la possibilità di utilizzare il file. Ho bisogno di un modo per garantire che le prime due chiamate siano già finite (invocate le loro richiamate) prima di proseguire senza nidificare le chiamate (e renderle sincrone in pratica).

Ho pensato di utilizzare gli emettitori di eventi sui callback e di registrare un contatore come ricevitore. Il contatore riceverebbe gli eventi finiti e conterebbe quante operazioni erano ancora in sospeso. Quando l'ultimo è terminato, eliminerebbe il file . Ma c'è il rischio di una condizione di gara e non sono sicuro che questo sia di solito come è fatta questa roba.

In che modo le persone Node risolvono questo tipo di problema?

+0

Grazie per questa domanda, anch'io ho un problema simile. –

risposta

91

Aggiornamento:

Ora io consiglierei di dare un'occhiata a:

  • Promises

    L'oggetto promessa viene utilizzato per calcoli differite e asincroni. Una promessa rappresenta un'operazione che non è ancora stata completata, ma è prevista per il in futuro.

    Una libreria di promesse popolari è bluebird. A consiglierei di dare un'occhiata a why promises.

    Si dovrebbe usare promette di trasformare questo:

    fs.readFile("file.json", function (err, val) { 
        if (err) { 
         console.error("unable to read file"); 
        } 
        else { 
         try { 
          val = JSON.parse(val); 
          console.log(val.success); 
         } 
         catch (e) { 
          console.error("invalid json in file"); 
         } 
        } 
    }); 
    

    In questa:

    fs.readFileAsync("file.json").then(JSON.parse).then(function (val) { 
        console.log(val.success); 
    }) 
    .catch(SyntaxError, function (e) { 
        console.error("invalid json in file"); 
    }) 
    .catch(function (e) { 
        console.error("unable to read file"); 
    }); 
    
  • generatori: Ad esempio attraverso co. controllo basato bontà flusso

    Generator per nodejs e il browser, utilizzando promesse, che ti permette scrivere il codice non-blocking in un modo carino-ish.

    var co = require('co'); 
    
    co(function *(){ 
        // yield any promise 
        var result = yield Promise.resolve(true); 
    }).catch(onerror); 
    
    co(function *(){ 
        // resolve multiple promises in parallel 
        var a = Promise.resolve(1); 
        var b = Promise.resolve(2); 
        var c = Promise.resolve(3); 
        var res = yield [a, b, c]; 
        console.log(res); 
        // => [1, 2, 3] 
    }).catch(onerror); 
    
    // errors can be try/catched 
    co(function *(){ 
        try { 
        yield Promise.reject(new Error('boom')); 
        } catch (err) { 
        console.error(err.message); // "boom" 
    } 
    }).catch(onerror); 
    
    function onerror(err) { 
        // log any uncaught errors 
        // co will not throw any errors you do not handle!!! 
        // HANDLE ALL YOUR ERRORS!!! 
        console.error(err.stack); 
    } 
    

Se ho capito bene penso che si dovrebbe avere uno sguardo al molto buono async biblioteca. Dovresti soprattutto dare un'occhiata allo series. Solo una copia dai frammenti di pagina GitHub:

async.series([ 
    function(callback){ 
     // do some stuff ... 
     callback(null, 'one'); 
    }, 
    function(callback){ 
     // do some more stuff ... 
     callback(null, 'two'); 
    }, 
], 
// optional callback 
function(err, results){ 
    // results is now equal to ['one', 'two'] 
}); 


// an example using an object instead of an array 
async.series({ 
    one: function(callback){ 
     setTimeout(function(){ 
      callback(null, 1); 
     }, 200); 
    }, 
    two: function(callback){ 
     setTimeout(function(){ 
      callback(null, 2); 
     }, 100); 
    }, 
}, 
function(err, results) { 
    // results is now equals to: {one: 1, two: 2} 
}); 

Come un plus questa libreria può anche eseguire nel browser.

+18

In realtà ho finito per utilizzare async.parallel, poiché le operazioni sono indipendenti e non volevo farle aspettare su quelle precedenti. –

2

La soluzione più semplice è quella di eseguire il fa_qualcosa * e scollegare in sequenza come segue:

do_something(tmp_file_name, function(err) { 
    do_something_other(tmp_file_name, function(err) { 
     fs.unlink(tmp_file_name); 
    }); 
}); 

A meno che, per motivi di prestazioni, si vuole eseguire fai_qualcosa() e do_something_other() in parallelo, consiglio di mantieni la semplicità e vai in questo modo.

21

Il modo più semplice incrementa un contatore intero quando si avvia un'operazione asincrona e quindi, nel callback, si diminuisce il contatore. A seconda della complessità, il callback potrebbe controllare il contatore per zero e quindi eliminare il file.

Un po 'più complesso sarebbe mantenere un elenco di oggetti, e ogni oggetto avrebbe tutti gli attributi necessari per identificare l'operazione (potrebbe anche essere la chiamata di funzione), nonché un codice di stato. I callback imposterebbero il codice di stato sul completamento.

Quindi si attende un ciclo (utilizzando process.nextTick) e si verifica se tutte le attività sono state completate. Il vantaggio di questo metodo sul contatore è che, se è possibile completare tutte le attività in sospeso, prima che vengano eseguite tutte le attività, la tecnica del contatore causerà la cancellazione prematura del file.

7

Non esiste una soluzione "nativa", ma esiste un million flow control libraries per il nodo. Potrebbe piacerti Step:

Step(
    function(){ 
     do_something(tmp_file_name, this.parallel()); 
     do_something_else(tmp_file_name, this.parallel()); 
    }, 
    function(err) { 
    if (err) throw err; 
    fs.unlink(tmp_file_name); 
    } 
) 

Oppure, come suggerito da Michael, i contatori potrebbero essere una soluzione più semplice. Dai un'occhiata a questo semaphore mock-up. Usereste in questo modo:

do_something1(file, queue('myqueue')); 
do_something2(file, queue('myqueue')); 

queue.done('myqueue', function(){ 
    fs.unlink(file); 
}); 
9
// simple countdown latch 
function CDL(countdown, completion) { 
    this.signal = function() { 
     if(--countdown < 1) completion(); 
    }; 
} 

// usage 
var latch = new CDL(10, function() { 
    console.log("latch.signal() was called 10 times."); 
}); 
1

Wait.for https://github.com/luciotato/waitfor

utilizzando Wait.for:

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

...in a fiber... 

wait.for(do_something,tmp_file_name); 
wait.for(do_something_other,tmp_file_name); 
fs.unlink(tmp_file_name); 
5

mi piacerebbe offrire un'altra soluzione che utilizza la velocità e l'efficienza del paradigma di programmazione al centro del nodo: eventi.

Tutto ciò che puoi fare con Promises o moduli progettati per gestire il controllo di flusso, come async, possono essere realizzati utilizzando eventi e una semplice macchina di stato, che a mio avviso offre una metodologia che è, forse, più facile da capire rispetto ad altri opzioni.

Per esempio assumere si desidera sommare la lunghezza delle file multipli in parallelo:

const EventEmitter = require('events').EventEmitter; 

// simple event-driven state machine 
const sm = new EventEmitter(); 

// running state 
let context={ 
    tasks: 0, // number of total tasks 
    active: 0, // number of active tasks 
    results: [] // task results 
}; 

const next = (result) => { // must be called when each task chain completes 

    if(result) { // preserve result of task chain 
    context.results.push(result); 
    } 

    // decrement the number of running tasks 
    context.active -= 1; 

    // when all tasks complete, trigger done state 
    if(!context.active) { 
    sm.emit('done'); 
    } 
}; 

// operational states 
// start state - initializes context 
sm.on('start', (paths) => { 
    const len=paths.length; 

    console.log(`start: beginning processing of ${len} paths`); 

    context.tasks = len;    // total number of tasks 
    context.active = len;    // number of active tasks 

    sm.emit('forEachPath', paths); // go to next state 
}); 

// start processing of each path 
sm.on('forEachPath', (paths)=>{ 

    console.log(`forEachPath: starting ${paths.length} process chains`); 

    paths.forEach((path) => sm.emit('readPath', path)); 
}); 

// read contents from path 
sm.on('readPath', (path) => { 

    console.log(` readPath: ${path}`); 

    fs.readFile(path,(err,buf) => { 
    if(err) { 
     sm.emit('error',err); 
     return; 
    } 
    sm.emit('processContent', buf.toString(), path); 
    }); 

}); 

// compute length of path contents 
sm.on('processContent', (str, path) => { 

    console.log(` processContent: ${path}`); 

    next(str.length); 
}); 

// when processing is complete 
sm.on('done',() => { 
    const total = context.results.reduce((sum,n) => sum + n); 
    console.log(`The total of ${context.tasks} files is ${total}`); 
}); 

// error state 
sm.on('error', (err) => { throw err; }); 

// ====================================================== 
// start processing - ok, let's go 
// ====================================================== 
sm.emit('start', ['file1','file2','file3','file4']); 

che sarà in uscita:

 
start: beginning processing of 4 paths 
forEachPath: starting 4 process chains 
    readPath: file1 
    readPath: file2 
    processContent: file1 
    readPath: file3 
    processContent: file2 
    processContent: file3 
    readPath: file4 
    processContent: file4 
The total of 4 files is 4021 

Si noti che l'ordinamento dei compiti catena di processo dipende dal sistema caricare.

si può immaginare il flusso del programma come:

 
start -> forEachPath -+-> readPath1 -> processContent1 -+-> done 
         +-> readFile2 -> processContent2 -+ 
         +-> readFile3 -> processContent3 -+ 
         +-> readFile4 -> processContent4 -+ 

Per il riutilizzo, sarebbe banale per creare un modulo per supportare i vari modelli di controllo di flusso, cioè serie, in parallelo, lotto, mentre, fino, ecc.

Problemi correlati