Node.js'de birden çok geri aramayı beklemenin deyimsel yolu


99

Bazı geçici dosyaya bağlı bazı işlemler yapmanız gerektiğini varsayalım. Burada Node'dan bahsettiğimiz için, bu işlemler açıkça eşzamansızdır. Geçici dosyanın ne zaman silinebileceğini bilmek için tüm işlemlerin bitmesini beklemenin deyimsel yolu nedir?

İşte yapmak istediğimi gösteren bazı kodlar:

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

Ama bu şekilde yazarsam, üçüncü çağrı, ilk ikisi dosyayı kullanma şansı bulmadan çalıştırılabilir. Aramaları iç içe geçirmeden (ve pratikte eşzamanlı hale getirmeden) devam etmeden önce ilk iki aramanın zaten bitmiş olduğunu (geri aramalarını başlattığını) garanti etmek için bir yola ihtiyacım var.

Geri aramalarda olay yayıcıları kullanmayı ve bir sayacı alıcı olarak kaydetmeyi düşündüm. Sayaç, bitmiş olayları alır ve kaç işlemin hala beklemede olduğunu sayar. Sonuncusu bittiğinde, dosyayı silecekti. Ancak bir yarış durumu riski var ve bunun genellikle böyle yapıldığından emin değilim.

Düğüm insanları bu tür bir sorunu nasıl çözer?


Bu soru için teşekkürler, bende de benzer bir sorun var.
Krishna Shetty

Yanıtlar:


94

Güncelleme:

Şimdi bir göz atmanızı tavsiye ederim:

  • Sözler

    Promise nesnesi, ertelenmiş ve zaman uyumsuz hesaplamalar için kullanılır. Söz, henüz tamamlanmamış ancak gelecekte beklenen bir operasyonu temsil eder.

    Popüler bir vaatler kütüphanesi bluebird'dür . A, neden vaatlere bir göz atmanızı tavsiye eder .

    Bunu değiştirmek için vaatler kullanmalısın:

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

    Bunun içine:

    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");
    });
  • jeneratörler: Örneğin co .

    Nodejs ve tarayıcı için oluşturucu tabanlı kontrol akışı iyiliği, sözler kullanarak, engellemeyen kodu hoş bir şekilde yazmanıza izin verir.

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

Doğru anladıysam , çok iyi eşzamansız kitaplığa bir göz atmanız gerektiğini düşünüyorum . Özellikle dizilere bir göz atmalısınız . Github sayfasındaki pasajlardan sadece bir kopya:

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

Bir artı olarak bu kitaplık tarayıcıda da çalışabilir.


21
İşlemler bağımsız olduğu için async.parallel kullanmayı bıraktım ve onları öncekileri bekletmek istemedim.
Thiago Arrais

22

En basit yol, zaman uyumsuz bir işlemi başlattığınızda bir tamsayı sayacını artırın ve ardından geri aramada sayacı azaltın. Karmaşıklığa bağlı olarak, geri arama, sayacı sıfır için kontrol edebilir ve ardından dosyayı silebilir.

Nesnelerin bir listesini tutmak biraz daha karmaşık olabilir ve her nesne, bir durum kodunun yanı sıra işlemi tanımlamak için ihtiyaç duyduğunuz herhangi bir niteliğe (işlev çağrısı bile olabilir) sahip olacaktır. Geri aramalar, durum kodunu tamamlandı olarak ayarlar.

O zaman bekleyen (kullanan process.nextTick) ve tüm görevlerin tamamlanıp tamamlanmadığını kontrol eden bir döngünüz olur. Bu yöntemin tezgah üzerindeki avantajı, tüm bekleyen görevlerin tüm görevler yayınlanmadan önce tamamlanması mümkünse, sayaç tekniğinin dosyayı vaktinden önce silmenize neden olmasıdır.


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

7

"Yerel" bir çözüm yoktur, ancak düğüm için bir milyon akış denetimi kitaplığı vardır . Step'i beğenebilirsiniz:

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

Veya Michael'ın önerdiği gibi sayaçlar daha basit bir çözüm olabilir. Bu semafor modeline bir göz atın . Bunu şu şekilde kullanırsın:

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

queue.done('myqueue', function(){
  fs.unlink(file);
});

6

Düğümün tam merkezinde yer alan programlama paradigmasının hızını ve verimliliğini kullanan başka bir çözüm sunmak istiyorum: olaylar.

Vaatler veya akış kontrolünü yönetmek için tasarlanmış modüller ile yapabileceğiniz her şey async, olaylar ve basit bir durum makinesi kullanılarak gerçekleştirilebilir; bu, belki de anlaşılması diğer seçeneklerden daha kolay olan bir metodoloji sunar.

Örneğin, birden çok dosyanın uzunluğunu paralel olarak toplamak istediğinizi varsayalım:

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

Hangi çıktı:

başlangıç: 4 yolun işlenmeye başlaması
forEachPath: 4 işlem zinciri başlatma
  readPath: file1
  readPath: file2
  processContent: file1
  readPath: file3
  processContent: file2
  processContent: file3
  readPath: file4
  processContent: file4
Toplam 4 dosya 4021

İşlem zinciri görevlerinin sırasının sistem yüküne bağlı olduğunu unutmayın.

Program akışını şu şekilde tasavvur edebilirsiniz:

start -> forEachPath - + -> readPath 1 -> processContent 1 - + -> done
                      + -> readFile 2 -> processContent 2 - +
                      + -> readFile 3 -> processContent 3 - +
                      + -> readFile 4 -> processContent 4 - +

Yeniden kullanım için, çeşitli akış kontrol modellerini, yani seri, paralel, toplu iş, while, kadar vb. Desteklemek için bir modül oluşturmak önemsiz olacaktır.


2

En basit çözüm, do_something * komutunu çalıştırmak ve aşağıdaki gibi sırayla bağlantıyı kaldırmaktır:

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

Performans nedenlerinden ötürü do_something () ve do_something_other () işlevlerini paralel olarak yürütmek istemediğiniz sürece, basit tutmanızı ve bu şekilde ilerlemenizi öneririm.



1

Saf Sözler ile biraz daha karmaşık olabilir, ancak Ertelenmiş Sözler kullanırsanız o kadar da kötü değil:

Yüklemek:

npm install --save @bitbar/deferred-promise

Kodunuzu değiştirin:

const DeferredPromise = require('@bitbar/deferred-promise');

const promises = [
  new DeferredPromise(),
  new DeferredPromise()
];

do_something(tmp_file_name, (err) => {
  if (err) {
    promises[0].reject(err);
  } else {
    promises[0].resolve();
  }
});

do_something_other(tmp_file_name, (err) => {
  if (err) {
    promises[1].reject(err);
  } else {
    promises[1].resolve();
  }
});

Promise.all(promises).then( () => {
  fs.unlink(tmp_file_name);
});
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.