2011-03-02 17 views
92

Supongamos que necesita realizar algunas operaciones que dependen de algún archivo temporal. Desde estamos hablando de Nodo aquí, esas operaciones son obviamente asincrónicas. ¿Cuál es la forma idiomática de esperar a que finalicen todas las operaciones para que sepa cuándo se puede eliminar el archivo temporal?Forma idiomática de esperar varias devoluciones de llamada en Node.js

Aquí hay un código que muestra lo que quiero hacer:

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

Pero si escribo de esta manera, la tercera llamada se puede ejecutar antes de los dos primeros tienen la oportunidad de utilizar el archivo. Necesito alguna forma de garantizar que las dos primeras llamadas ya hayan finalizado (invocaron sus devoluciones de llamada) antes de continuar sin anidar las llamadas (y hacerlas sincrónicas en la práctica).

Pensé en usar emisores de eventos en las devoluciones de llamada y registrar un contador como receptor. El contador recibiría los eventos finalizados y contaría cuántas operaciones de aún estaban pendientes. Cuando termine el último, eliminará el archivo . Pero existe el riesgo de una condición de carrera y no estoy seguro de que esto sea , por lo general, cómo se hace esto.

¿Cómo resuelve la gente este tipo de problema?

+0

Gracias por esta pregunta, yo también tengo un problema similar. –

Respuesta

91

Actualización:

Ahora yo le aconsejaría a echar un vistazo a:

  • Promises

    El objeto Promise se utiliza en los cálculos diferidos y asíncronos. Una promesa representa una operación que aún no se ha completado, pero se espera en el futuro.

    Una popular biblioteca de promesas es bluebird. A aconsejaría echar un vistazo a why promises.

    Debe utilizar promesas de convertir esto:

    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"); 
         } 
        } 
    }); 
    

    En esto:

    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"); 
    }); 
    
  • generadores: Por ejemplo a través de co.

    Generador de control basado en la bondad de flujo para nodejs y el navegador, utilizando promesas, lo que le permite escribir código sin bloqueo de una manera agradable-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); 
    } 
    

Si he entendido bien creo que deberías echar un vistazo a la muy buena async biblioteca. Especialmente debería echarle un vistazo al series. Sólo una copia de los fragmentos de la página 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} 
}); 

Como un plus esta biblioteca también se puede ejecutar en el navegador.

+18

De hecho terminé usando async.parallel, ya que las operaciones son independientes y no quería hacerlas esperar en las anteriores. –

2

La solución más sencilla es ejecutar el hacer_algo * y desvincular en la secuencia de la siguiente manera:

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

A menos que, por razones de rendimiento, que quiere ejecutar hacer_algo() y do_something_other() en paralelo, sugiero a manténlo simple y ve por aquí.

21

La manera más simple de incrementar un contador entero cuando se inicia una operación asíncrona y luego, en la devolución de llamada, disminuir el contador. Dependiendo de la complejidad, la devolución de llamada podría verificar el contador para cero y luego eliminar el archivo.

Un poco más complejo sería mantener una lista de objetos, y cada objeto tendría los atributos que necesita para identificar la operación (incluso podría ser la llamada a la función), así como un código de estado. Las devoluciones de llamada establecerían el código de estado en completado.

Luego tendría un bucle que espera (usando process.nextTick) y verifica si todas las tareas están completas. La ventaja de este método en el mostrador es que, si es posible que se completen todas las tareas pendientes, antes de que se emitan todas las tareas, la técnica del contador hará que elimine el archivo prematuramente.

7

No existe una solución "nativa", pero hay un million flow control libraries para el nodo. Es posible que le guste Paso:

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

O, como Michael sugirió, los contadores podrían ser una solución más simple. Eche un vistazo a este semaphore mock-up. Se podría utilizar de esta manera:

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

usando 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

Me gustaría ofrecer otra solución que utiliza la velocidad y la eficiencia del paradigma de programación en el núcleo de Nodo: eventos.

Todo lo que puede hacer con promesas o módulos diseñados para administrar el control de flujo, como async, se puede lograr usando eventos y una máquina de estado simple, que creo que ofrece una metodología que es quizás más fácil de comprender que otras opciones.

Por ejemplo supongamos que desea sumar la longitud de varios archivos en paralelo:

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']); 

Cuál sería:

 
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 

Tenga en cuenta que el orden de las tareas de la cadena de proceso depende del sistema carga.

que puedan visualizar el flujo del programa como:

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

para su reutilización, sería trivial para crear un módulo para soportar los diversos patrones de control de flujo, es decir, serie, paralelo, por lotes, mientras que, hasta que, etc.

Cuestiones relacionadas