Node.js में कई कॉलबैक के लिए प्रतीक्षा करने का मुहावरेदार तरीका


99

मान लें कि आपको कुछ ऑपरेशन करने की ज़रूरत है जो कुछ अस्थायी फ़ाइल पर निर्भर करते हैं। चूँकि हम यहाँ नोड के बारे में बात कर रहे हैं, वे ऑपरेशन स्पष्ट रूप से अतुल्यकालिक हैं। जब अस्थायी फ़ाइल को हटाया जा सकता है, तो यह जानने के लिए कि सभी ऑपरेशनों को समाप्त करने के लिए इंतजार करने का मुहावरेदार तरीका क्या है?

यहाँ कुछ कोड दिखाया गया है जो मैं करना चाहता हूँ:

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

लेकिन अगर मैं इसे इस तरह लिखता हूं, तो पहले दो को फ़ाइल का उपयोग करने का मौका मिलने से पहले तीसरी कॉल को निष्पादित किया जा सकता है। मुझे यह गारंटी देने के लिए किसी तरह की आवश्यकता है कि पहले दो कॉल पहले से ही समाप्त हो गए हैं (कॉल को रद्द करने से पहले) कॉल को घोंसले के बिना आगे बढ़ने से पहले (और उन्हें अभ्यास में तुल्यकालिक बना रहे हैं)।

मैंने कॉलबैक पर ईवेंट एमिटर का उपयोग करने और एक काउंटर को रिसीवर के रूप में पंजीकृत करने के बारे में सोचा। काउंटर समाप्त घटनाओं को प्राप्त करेगा और गिनेगा कि कितने ऑपरेशन अभी भी लंबित हैं। जब अंतिम समाप्त हो जाता है, तो यह फ़ाइल को हटा देगा। लेकिन एक दौड़ की स्थिति का खतरा है और मुझे यकीन नहीं है कि आमतौर पर यह सामान कैसे किया जाता है।

नोड लोग इस तरह की समस्या का समाधान कैसे करते हैं?


इस सवाल के लिए धन्यवाद, मेरे पास भी ऐसा ही मुद्दा है।
कृष्ण शेट्टी

जवाबों:


94

अपडेट करें:

अब मैं सलाह दूंगा:

  • वादे

    प्रॉमिस ऑब्जेक्ट का उपयोग आस्थगित और अतुल्यकालिक गणनाओं के लिए किया जाता है। एक वादा एक ऑपरेशन का प्रतिनिधित्व करता है जो अभी तक पूरा नहीं हुआ है, लेकिन भविष्य में उम्मीद है।

    एक लोकप्रिय वादा पुस्तकालय ब्लूबर्ड है । ए इस बात पर ध्यान देने की सलाह देगा कि वादे क्यों किए जाते हैं

    इसे चालू करने के लिए आपको वादों का उपयोग करना चाहिए:

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

    इस मामले में:

    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");
    });
  • जनरेटर: उदाहरण के लिए सह के माध्यम से ।

    नोड्स और ब्राउज़र के लिए जेनरेटर आधारित नियंत्रण प्रवाह अच्छाई, वादों का उपयोग करते हुए, आप एक अच्छा-ईश तरीके से गैर-अवरोधक कोड लिखने देते हैं।

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

अगर मुझे सही से समझ में आता है तो मुझे लगता है कि आपको बहुत अच्छे async लाइब्रेरी पर एक नज़र डालनी चाहिए । आपको विशेष रूप से श्रृंखला पर एक नजर डालनी चाहिए । जीथब पृष्ठ से स्निपेट की एक प्रति:

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

प्लस के रूप में यह लाइब्रेरी ब्राउज़र में भी चल सकती है।


21
मैं वास्तव में async.parallel का उपयोग करके समाप्त हो गया, क्योंकि ऑपरेशन स्वतंत्र हैं और मैं उन्हें पिछले वाले पर इंतजार नहीं करना चाहता था।
थियागो अर्रिस

22

जब आप कोई async कार्रवाई प्रारंभ करते हैं और तब कॉलबैक में, काउंटर को घटाते हैं, तो एक पूर्णांक काउंटर बढ़ाता है। जटिलता के आधार पर, कॉलबैक शून्य के लिए काउंटर की जांच कर सकता है और फिर फ़ाइल को हटा सकता है।

वस्तुओं की एक सूची को बनाए रखने के लिए थोड़ा अधिक जटिल होगा, और प्रत्येक ऑब्जेक्ट में कोई भी विशेषता होगी जिसे आपको ऑपरेशन की पहचान करने की आवश्यकता है (यह फ़ंक्शन कॉल भी हो सकता है) और साथ ही एक स्थिति कोड भी। कॉलबैक पूर्ण होने के लिए स्थिति कोड सेट करेगा।

तब आपके पास एक लूप होगा जो प्रतीक्षा करता है (उपयोग करके process.nextTick) और यह देखने के लिए जांचता है कि क्या सभी कार्य पूरे हो गए हैं। काउंटर पर इस पद्धति का लाभ यह है कि यदि सभी कार्यों को पूरा करने से पहले सभी कार्यों को पूरा करना संभव है, तो काउंटर तकनीक आपको समय से पहले फ़ाइल को हटाने का कारण बनेगी।


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

कोई "देशी" समाधान नहीं है, लेकिन नोड के लिए एक लाख प्रवाह नियंत्रण पुस्तकालय हैं । आप कदम पसंद कर सकते हैं:

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

या, जैसा कि माइकल ने सुझाव दिया, काउंटर एक सरल समाधान हो सकते हैं। इस सेमाफोर मॉक-अप पर एक नज़र डालें । आप इसे इस तरह उपयोग करेंगे:

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

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

6

मैं एक और समाधान पेश करना चाहता हूं जो कि नोड के बहुत ही मुख्य स्थान पर प्रोग्रामिंग प्रतिमान की गति और दक्षता का उपयोग करता है: घटनाएं।

सब कुछ आप प्रवाह-नियंत्रण का प्रबंधन करने के लिए डिज़ाइन किए गए वादे या मॉड्यूल के साथ कर सकते हैं, जैसे async, घटनाओं और एक सरल राज्य-मशीन का उपयोग करके पूरा किया जा सकता है, जो मुझे लगता है कि एक पद्धति प्रदान करता है जो, शायद, अन्य विकल्पों की तुलना में समझने में आसान है।

उदाहरण के लिए मान लें कि आप समानांतर में कई फ़ाइलों की लंबाई का योग करना चाहते हैं:

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

जो आउटपुट देगा:

शुरू: 4 रास्तों का प्रसंस्करण
forEachPath: 4 प्रोसेस चेन शुरू करना
  readPath: file1
  readPath: file2
  processContent: file1
  readPath: file3
  प्रक्रियासंबंधी: file2
  processContent: file3
  readPath: file4
  processContent: file4
कुल 4 फाइलें 4021 हैं

ध्यान दें कि प्रक्रिया श्रृंखला कार्यों का क्रम सिस्टम लोड पर निर्भर है।

आप कार्यक्रम प्रवाह की कल्पना कर सकते हैं:

start -> forEachPath - + -> readPath 1 -> processContent 1 - + -> किया
                      + -> readFile 2 -> प्रक्रियासंपर्क 2 - +
                      + -> readFile 3 -> processContent 3 - +
                      + -> readFile 4 -> processContent 4 - +

पुन: उपयोग के लिए, विभिन्न प्रवाह नियंत्रण पैटर्न, अर्थात श्रृंखला, समानांतर, बैच, जबकि, जब तक, आदि का समर्थन करने के लिए एक मॉड्यूल बनाना तुच्छ होगा।


2

सबसे सरल उपाय do_something * चलाना और क्रम में अनलिंक करना है:

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

जब तक, प्रदर्शन कारणों के लिए, आप समानांतर में do_something () और do_something_other () निष्पादित करना चाहते हैं, मैं इसे सरल रखने और इस तरह से जाने का सुझाव देता हूं।



1

शुद्ध वादों के साथ यह थोड़ा अधिक गन्दा हो सकता है, लेकिन यदि आप आस्थगित वादों का उपयोग करते हैं तो यह इतना बुरा नहीं है:

इंस्टॉल:

npm install --save @bitbar/deferred-promise

अपना कोड संशोधित करें:

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);
});
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.