Node.js में अतुल्यकालिक कार्यों के लंबे घोंसले के शिकार से कैसे बचें


158

मैं एक पेज बनाना चाहता हूं जो डीबी से कुछ डेटा प्रदर्शित करता है, इसलिए मैंने कुछ फ़ंक्शन बनाए हैं जो उस डेटा को मेरे डीबी से प्राप्त करते हैं। मैं Node.js में सिर्फ एक नौसिखिया हूं, जहां तक ​​मैं समझता हूं, अगर मैं उन सभी को एक पृष्ठ (HTTP प्रतिक्रिया) में उपयोग करना चाहता हूं तो मुझे उन सभी को घोंसला बनाना होगा:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

यदि इस तरह के कई कार्य हैं, तो घोंसले का शिकार एक समस्या बन जाती है

इससे बचने का कोई रास्ता है क्या? मुझे लगता है कि इसके साथ आपको कई अतुल्यकालिक कार्यों को कैसे करना है, जो कुछ मौलिक प्रतीत होता है।


12
तो जब आपके पास 10 एसिंक्स फ़ंक्शन हैं, तो आपके पास इंडेंटेशन के 10 स्तर हैं?
काय पाले

यह लिंक मदद कर सकता है। stackoverflow.com/a/4631909/290340
Evan Plaice

1
एक और समस्या: कई अन्य लाइनों के इंडेंटेशन को बदलने के बीच getSomeDateऔर getSomeOtherDateअंत में एक और फ़ंक्शन सम्मिलित करना, जो गिट इतिहास को पढ़ने के लिए कठिन बना देता है ( git blameइसके बाद भी बेकार है), और आप मैन्युअल रूप से इसे करते समय बग बनाते हैं
डैनियल एल्डर

जवाबों:


73

दिलचस्प अवलोकन। ध्यान दें कि जावास्क्रिप्ट में आप नामांकित फ़ंक्शन चर के साथ आमतौर पर इनलाइन अनाम कॉलबैक फ़ंक्शन को बदल सकते हैं।

निम्नलिखित:

http.createServer(function (req, res) {
   // inline callback function ...

   getSomeData(client, function (someData) {
      // another inline callback function ...

      getMoreData(client, function(moreData) {
         // one more inline callback function ...
      });
   });

   // etc ...
});

कुछ इस तरह से देखने के लिए फिर से लिखा जा सकता है:

var moreDataParser = function (moreData) {
   // date parsing logic
};

var someDataParser = function (someData) {
   // some data parsing logic

   getMoreData(client, moreDataParser);
};

var createServerCallback = function (req, res) {
   // create server logic

   getSomeData(client, someDataParser);

   // etc ...
};

http.createServer(createServerCallback);

हालाँकि जब तक आप अन्य स्थानों पर कॉलबैक तर्क का पुन: उपयोग करने की योजना नहीं बनाते हैं, तब तक इनलाइन अनाम फ़ंक्शन को पढ़ना बहुत आसान होता है, जैसा कि आपके उदाहरण में। यह आपको सभी कॉलबैक के लिए एक नाम खोजने से भी बचेगा।

इसके अलावा ध्यान दें कि जैसा कि @pst ने नीचे टिप्पणी में लिखा है, यदि आप आंतरिक क्रियाओं के भीतर क्लोजर वेरिएबल्स तक पहुंच रहे हैं, तो ऊपर का सीधा अनुवाद नहीं होगा। ऐसे मामलों में, इनलाइन अनाम फ़ंक्शंस का उपयोग करना और भी बेहतर है।


26
हालांकि, (और वास्तव में सिर्फ व्यापार बंद को समझने के लिए) जब संयुक्त राष्ट्र का नेस्टेड होता है, तो चर पर कुछ करीबी शब्दार्थ खो सकते हैं, इसलिए इसका सीधा अनुवाद नहीं है। उपरोक्त उदाहरण में 'रेस' तक पहुंच getMoreDataखो गई है।

2
मुझे लगता है कि आपका समाधान टूट गया है: someDataParserवास्तव में सभी डेटा को पार्स करता है, क्योंकि यह भी कॉल करता है getMoreData। उस अर्थ में, फ़ंक्शन नाम गलत है और यह स्पष्ट हो जाता है कि हमने वास्तव में नेस्टिंग समस्या को दूर नहीं किया है।
कॉन्स्टेंटिन शुबर्ट

63

Kay, बस इनमें से एक मॉड्यूल का उपयोग करें।

यह इसे बदल देगा:

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', 'bobvance@potato.egg', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

इस मामले में:

flow.exec(
    function() {
        dbGet('userIdOf:bobvance', this);

    },function(userId) {
        dbSet('user:' + userId + ':email', 'bobvance@potato.egg', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

    },function() {
        okWeAreDone()
    }
);

9
फ्लो-जेएस, स्टेप और एसिंक्स पर एक त्वरित नज़र थी और ऐसा लगता है कि वे केवल फ़ंक्शन निष्पादन के आदेश से निपटते हैं। मेरे मामले में हर इंडेंटेशन में इनलाइन क्लोजर वेरिएबल्स तक पहुंच है। इसलिए उदाहरण के लिए कार्य इस तरह से काम करते हैं: HTTP req / Res प्राप्त करें, कुकी के लिए DB से userid प्राप्त करें, बाद के उपयोगकर्ता के लिए ईमेल प्राप्त करें, बाद के ईमेल के लिए अधिक डेटा प्राप्त करें, ..., बाद में Y के लिए X प्राप्त करें ... यदि मैं गलत नहीं हूं, तो ये रूपरेखा केवल आश्वासन देती है कि एसिंक्स फ़ंक्शन को सही क्रम में निष्पादित किया जाएगा, लेकिन हर फ़ंक्शन बॉडी में क्लोजर द्वारा स्वाभाविक रूप से प्रदान किए गए चर को प्राप्त करने का कोई तरीका नहीं है (धन्यवाद?) धन्यवाद :)
Kay Pale

9
इन पुस्तकालयों की रैंकिंग के संदर्भ में, मैंने गितुब में हर एक पर "सितारे" की संख्या की जाँच की। async में लगभग 3000 के साथ सबसे अधिक है, चरण लगभग 1000 के साथ है, अन्य काफी कम हैं। बेशक, वे सभी एक ही काम नहीं करते हैं :-)
kgilpin

3
@KayPale मैं async.waterfall का उपयोग करता हूं, और कभी-कभी प्रत्येक चरण / चरण के लिए मेरे अपने कार्य होंगे जो अगले चरण की आवश्यकता के साथ गुजरेंगे, या async.METHOD कॉल से पहले चर को परिभाषित करेंगे ताकि यह डाउनलाइन उपलब्ध हो। इसके अलावा, मेरे async। * कॉल्स के लिए METHODNAME.bind (...) का उपयोग करेंगे, जो बहुत अच्छी तरह से काम करता है।
ट्रैकर

एक त्वरित प्रश्न: आपके मॉड्यूल की सूची में, पिछले दो समान हैं? Ie "async.js" और "async"
dari0h

18

अधिकांश भाग के लिए, मैं डैनियल वैसलो से सहमत हूँ। यदि आप एक जटिल और गहरी नेस्टेड फ़ंक्शन को अलग-अलग नामित कार्यों में तोड़ सकते हैं, तो यह आमतौर पर एक अच्छा विचार है। ऐसे समय के लिए जब यह किसी एक फ़ंक्शन के अंदर करने के लिए समझ में आता है, तो आप उपलब्ध कई नोड में से एक का उपयोग कर सकते हैं। लोग इससे निपटने के लिए कई तरह के तरीके अपनाते हैं, इसलिए नोड.जेएस मॉड्यूल पृष्ठ पर एक नज़र डालें और देखें कि आप क्या सोचते हैं।

मैंने खुद इसके लिए एक मॉड्यूल लिखा है, जिसे async.js कहा जाता है । इसका उपयोग करते हुए, उपरोक्त उदाहरण को अपडेट किया जा सकता है:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  async.series({
    someData: async.apply(getSomeDate, client),
    someOtherData: async.apply(getSomeOtherDate, client),
    moreData: async.apply(getMoreData, client)
  },
  function (err, results) {
    var html = "<h1>Demo page</h1>";
    html += "<p>" + results.someData + "</p>";
    html += "<p>" + results.someOtherData + "</p>";
    html += "<p>" + results.moreData + "</p>";
    res.write(html);
    res.end();
  });
});

इस दृष्टिकोण के बारे में एक अच्छी बात यह है कि आप 'श्रृंखला' फ़ंक्शन को 'समानांतर' में बदलकर डेटा को समान रूप से लाने के लिए अपने कोड को जल्दी से बदल सकते हैं। क्या अधिक है, async.js भी ब्राउज़र के अंदर काम करेगा, इसलिए आप उन्हीं तरीकों का उपयोग कर सकते हैं जैसे आप नोड में होंगे। आपको किसी भी ट्रिकी async कोड का सामना करना चाहिए।

आशा है कि उपयोगी है!


हाय Caolan और उत्तर के लिए धन्यवाद! मेरे मामले में हर इंडेंटेशन में इनलाइन क्लोजर वेरिएबल्स तक पहुंच है। उदाहरण के लिए फ़ंक्शन इस तरह से काम करते हैं: HTTP रीक / रेस प्राप्त करें, कुकी के लिए DB से userid प्राप्त करें, बाद के उपयोगकर्ता के लिए ईमेल प्राप्त करें, बाद के ईमेल के लिए अधिक डेटा प्राप्त करें, ..., बाद में Y के लिए X प्राप्त करें ... यदि मैं गलत नहीं हूँ, तो आप जो कोड सुझाते हैं, वह केवल यह बताता है कि async फ़ंक्शंस को सही क्रम में निष्पादित किया जाएगा, लेकिन हर फ़ंक्शन बॉडी में मेरे मूल कोड में क्लोज़र द्वारा स्वाभाविक रूप से प्रदान किए गए चर को प्राप्त करने का तरीका नहीं है। क्या यह मामला है?
काय पाले

3
आप जो हासिल करने की कोशिश कर रहे हैं, उसे वास्तुशिल्प रूप से डेटा पाइपलाइन कहा जाता है। आप इस तरह के मामलों के लिए एसिंक्स जलप्रपात का उपयोग कर सकते हैं।
रुडोल्फ मेजरिंग

18

आप नेस्टेड फ़ंक्शंस या एक मॉड्यूल के बजाय एक सरणी के साथ इस ट्रिक का उपयोग कर सकते हैं।

आँखों पर सुगमता।

var fs = require("fs");
var chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step3");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step4");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step5");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("done");
    },
];
chain.shift()();

आप समानांतर प्रक्रियाओं या प्रक्रियाओं की समानांतर श्रृंखला के लिए मुहावरे का विस्तार कर सकते हैं:

var fs = require("fs");
var fork1 = 2, fork2 = 2, chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        var next = chain.shift();
        fs.stat("f2a.js",next);
        fs.stat("f2b.js",next);
    },
    function(err, stats) {
        if ( --fork1 )
            return;
        console.log("step3");
        var next = chain.shift();

        var chain1 = [
            function() { 
                console.log("step4aa");
                fs.stat("f1.js",chain1.shift());
            },
            function(err, stats) { 
                console.log("step4ab");
                fs.stat("f1ab.js",next);
            },
        ];
        chain1.shift()();

        var chain2 = [
            function() { 
                console.log("step4ba");
                fs.stat("f1.js",chain2.shift());
            },
            function(err, stats) { 
                console.log("step4bb");
                fs.stat("f1ab.js",next);
            },
        ];
        chain2.shift()();
    },
    function(err, stats) {
        if ( --fork2 )
            return;
        console.log("done");
    },
];
chain.shift()();

15

मुझे इस उद्देश्य के लिए async.js बहुत पसंद है ।

समस्या का समाधान झरना कमांड द्वारा किया जाता है:

झरना (कार्य, [कॉलबैक])

श्रृंखला में फ़ंक्शन के एक सरणी को चलाता है, प्रत्येक अपने परिणाम को सरणी में अगले तक ले जाता है। हालाँकि, यदि कोई फ़ंक्शन कॉलबैक में कोई त्रुटि देता है, तो अगला फ़ंक्शन निष्पादित नहीं किया जाता है और त्रुटि के साथ मुख्य कॉलबैक को तुरंत कॉल किया जाता है।

तर्क

कार्य - कार्यों को चलाने के लिए एक सरणी, प्रत्येक फ़ंक्शन को कॉलबैक (गलती से, परिणाम 1, परिणाम 2, ...) पास किया जाता है, इसे पूरा होने पर कॉल करना होगा। पहला तर्क एक त्रुटि है (जो शून्य हो सकती है) और अगले कार्य के लिए किसी भी अन्य तर्क को तर्क के रूप में पारित किया जाएगा। कॉलबैक (ग़लती से, [परिणाम]) - सभी कार्यों के पूरा होने के बाद एक वैकल्पिक कॉलबैक। यह अंतिम कार्य के कॉलबैक के परिणाम पारित किया जाएगा।

उदाहरण

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'    
});

Req, res चरों के रूप में, उन्हें फंक्शन (req, res) {} के रूप में एक ही दायरे में साझा किया जाएगा, जिसने पूरे async.waterfall कॉल को संलग्न किया।

इतना ही नहीं, async बहुत साफ है। मेरे कहने का मतलब यह है कि मैं इस तरह के बहुत सारे मामले बदल देता हूं:

function(o,cb){
    function2(o,function(err, resp){
        cb(err,resp);
    })
}

पहले करने के लिए:

function(o,cb){
    function2(o,cb);
}

इसके बाद:

function2(o,cb);

इसके बाद:

async.waterfall([function2,function3,function4],optionalcb)

यह async के लिए तैयार किए गए कई प्रीमियर कार्यों को भी उपयोग में लाने की अनुमति देता है। बहुत तेजी से। बस आप क्या करना चाहते हैं, यह सुनिश्चित करें कि ओ, सीबी सार्वभौमिक रूप से संभाला जाता है। यह पूरी कोडिंग प्रक्रिया को बहुत तेज करता है।


11

आपको जो आवश्यक है वह थोड़ा सा चीनी है। इसे बाहर निकालें:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = ["<h1>Demo page</h1>"];
  var pushHTML = html.push.bind(html);

  Queue.push( getSomeData.partial(client, pushHTML) );
  Queue.push( getSomeOtherData.partial(client, pushHTML) );
  Queue.push( getMoreData.partial(client, pushHTML) );
  Queue.push( function() {
    res.write(html.join(''));
    res.end();
  });
  Queue.execute();
}); 

बहुत साफ है , है ना? आप देख सकते हैं कि HTML एक सरणी बन गई है। यह आंशिक रूप से है क्योंकि तार अपरिवर्तनीय हैं, इसलिए आप अपने आउटपुट को सरणी में बेहतर बनाने के साथ, बड़े और बड़े तारों को छोड़ने की तुलना में बेहतर करते हैं। अन्य कारण के साथ एक और अच्छा वाक्यविन्यास है bind

Queueउदाहरण में वास्तव में सिर्फ एक उदाहरण है और साथ partialही निम्नानुसार लागू किया जा सकता है

// Functional programming for the rescue
Function.prototype.partial = function() {
  var fun = this,
      preArgs = Array.prototype.slice.call(arguments);
  return function() {
    fun.apply(null, preArgs.concat.apply(preArgs, arguments));
  };
};

Queue = [];
Queue.execute = function () {
  if (Queue.length) {
    Queue.shift()(Queue.execute);
  }
};

1
Queue.execute () बस async कॉल से परिणामों की प्रतीक्षा किए बिना, एक के बाद एक विभाजन को निष्पादित करेगा।
ngn

स्पॉट ऑन, धन्यवाद। मैंने जवाब अपडेट कर दिया है। यहाँ एक परीक्षण है: jsbin.com/ebobo5/edit (एक वैकल्पिक lastकार्य के साथ)
gblazex

हाय galambalazs और उत्तर के लिए धन्यवाद! मेरे मामले में हर इंडेंटेशन में इनलाइन क्लोजर वेरिएबल्स तक पहुंच है। उदाहरण के लिए फ़ंक्शन इस तरह से काम करते हैं: HTTP रीक / रेस प्राप्त करें, कुकी के लिए DB से userid प्राप्त करें, बाद के उपयोगकर्ता के लिए ईमेल प्राप्त करें, बाद के ईमेल के लिए अधिक डेटा प्राप्त करें, ..., बाद में Y के लिए X प्राप्त करें ... यदि मैं गलत नहीं हूँ, तो आप जो कोड सुझाते हैं, वह केवल यह बताता है कि async फ़ंक्शंस को सही क्रम में निष्पादित किया जाएगा, लेकिन हर फ़ंक्शन बॉडी में मेरे मूल कोड में क्लोज़र द्वारा स्वाभाविक रूप से प्रदान किए गए चर को प्राप्त करने का तरीका नहीं है। क्या यह मामला है?
काय पाले

1
खैर आप निश्चित रूप से सभी उत्तरों में बंद खो देते हैं। आप साझा डेटा के लिए वैश्विक दायरे में एक वस्तु बनाने के लिए क्या कर सकते हैं । इसलिए जैसे आपका पहला फ़ंक्शन जोड़ता है obj.emailऔर आपका अगला फ़ंक्शन obj.emailतब इसे हटाता है (या सिर्फ असाइन करता है null)।
gblazex 21

7

जब से मैंने इसे पाया है, तब से असिन.जैसे प्यार में हूँ । यह एक async.seriesफ़ंक्शन है जिसका उपयोग आप लंबे घोंसले के शिकार से बचने के लिए कर सकते हैं।

प्रलेखन: -


श्रृंखला (कार्य, [कॉलबैक])

श्रृंखला में कार्यों की एक सरणी चलाएँ, पिछले फ़ंक्शन के पूरा होने के बाद हर एक चल रहा है। [...]

तर्क

tasks- चलाने के लिए कार्यों की एक सरणी, प्रत्येक फ़ंक्शन को कॉलबैक पास किया जाता है जिसे पूरा होने पर कॉल करना चाहिए। callback(err, [results])- सभी कार्यों को पूरा करने के बाद एक वैकल्पिक कॉलबैक चलाने के लिए। इस फ़ंक्शन को सरणी में उपयोग किए जाने वाले कॉलबैक में पारित किए गए सभी तर्कों का एक सरणी मिलती है।


यहाँ हम इसे आपके उदाहरण कोड पर कैसे लागू कर सकते हैं: -

http.createServer(function (req, res) {

    res.writeHead(200, {'Content-Type': 'text/html'});

    var html = "<h1>Demo page</h1>";

    async.series([
        function (callback) {
            getSomeData(client, function (someData) { 
                html += "<p>"+ someData +"</p>";

                callback();
            });
        },

        function (callback) {
            getSomeOtherData(client, function (someOtherData) { 
                html += "<p>"+ someOtherData +"</p>";

                callback(); 
            });
        },

        funciton (callback) {
            getMoreData(client, function (moreData) {
                html += "<p>"+ moreData +"</p>";

                callback();
            });
        }
    ], function () {
        res.write(html);
        res.end();
    });
});

6

सबसे सरल वाक्य रचना चीनी मैंने देखा है नोड-वादा।

npm स्थापित नोड-वादा || git clone https://github.com/kriszyp/node-promise

इसका उपयोग करके आप async विधियों की श्रृंखला बना सकते हैं:

firstMethod().then(secondMethod).then(thirdMethod);

प्रत्येक का रिटर्न मान अगले में तर्क के रूप में उपलब्ध है।


3

आपने जो कुछ किया है, वह एक एसाइन पैटर्न है और इसे 3 कार्यों के लिए क्रमानुसार लागू किया जाता है, हर एक को शुरू करने से पहले पूरा होने की प्रतीक्षा है - अर्थात आपने उन्हें तुल्यकालिक बनाया है । Asynch प्रोग्रामिंग के बारे में बात यह है कि आपके पास कई कार्य एक साथ चल सकते हैं और प्रत्येक को पूरा होने के लिए इंतजार नहीं करना पड़ता है।

अगर getSomeDate () getSomeOtherDate () को कुछ भी प्रदान नहीं करता है, जो getMoreData () को कुछ भी प्रदान नहीं करता है, तो आप उन्हें asynchronously के रूप में js की अनुमति नहीं देते हैं या यदि वे अन्योन्याश्रित हैं (और अतुल्यकालिक नहीं हैं) उन्हें एक के रूप में लिखें एकल समारोह?

आपको प्रवाह को नियंत्रित करने के लिए नेस्टिंग का उपयोग करने की आवश्यकता नहीं है - उदाहरण के लिए, प्रत्येक फ़ंक्शन को एक सामान्य फ़ंक्शन को कॉल करके समाप्त करने के लिए प्राप्त करें जो यह निर्धारित करता है कि सभी 3 पूर्ण हो गए हैं और फिर प्रतिक्रिया भेजता है।


2

मान लीजिए आप ऐसा कर सकते हैं:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var html = "<h1>Demo page</h1>";
    chain([
        function (next) {
            getSomeDate(client, next);
        },
        function (next, someData) {
            html += "<p>"+ someData +"</p>";
            getSomeOtherDate(client, next);
        },
        function (next, someOtherData) {
            html += "<p>"+ someOtherData +"</p>";
            getMoreData(client, next);
        },
        function (next, moreData) {
            html += "<p>"+ moreData +"</p>";
            res.write(html);
            res.end();
        }
    ]);
});

आपको केवल श्रृंखला () लागू करने की आवश्यकता है ताकि यह प्रत्येक फ़ंक्शन को अगले एक पर आंशिक रूप से लागू करे, और तुरंत केवल पहले फ़ंक्शन को लागू करे:

function chain(fs) {
    var f = function () {};
    for (var i = fs.length - 1; i >= 0; i--) {
        f = fs[i].partial(f);
    }
    f();
}

हाय ngn और जवाब के लिए धन्यवाद! मेरे मामले में हर इंडेंटेशन में इनलाइन क्लोजर वेरिएबल्स तक पहुंच है। उदाहरण के लिए फ़ंक्शन इस तरह से काम करते हैं: HTTP रीक / रेस प्राप्त करें, कुकी के लिए DB से userid प्राप्त करें, बाद के उपयोगकर्ता के लिए ईमेल प्राप्त करें, बाद के ईमेल के लिए अधिक डेटा प्राप्त करें, ..., बाद में Y के लिए X प्राप्त करें ... यदि मैं गलत नहीं हूँ, तो आप जो कोड सुझाते हैं, वह केवल यह बताता है कि async फ़ंक्शंस को सही क्रम में निष्पादित किया जाएगा, लेकिन हर फ़ंक्शन बॉडी में मेरे मूल कोड में क्लोज़र द्वारा स्वाभाविक रूप से प्रदान किए गए चर को प्राप्त करने का तरीका नहीं है। क्या यह मामला है?
काय पाले

2

कॉलबैक के साथ शुद्ध जावास्क्रिप्ट में कॉलबैक नरक से आसानी से बचा जा सकता है। नीचे दिए गए समाधान सभी कॉलबैक फ़ंक्शन (त्रुटि, डेटा) हस्ताक्षर का पालन करते हैं।

http.createServer(function (req, res) {
  var modeNext, onNext;

  // closure variable to keep track of next-callback-state
  modeNext = 0;

  // next-callback-handler
  onNext = function (error, data) {
    if (error) {
      modeNext = Infinity;
    } else {
      modeNext += 1;
    }
    switch (modeNext) {

    case 0:
      res.writeHead(200, {'Content-Type': 'text/html'});
      var html = "<h1>Demo page</h1>";
      getSomeDate(client, onNext);
      break;

    // handle someData
    case 1:
        html += "<p>"+ data +"</p>";
        getSomeOtherDate(client, onNext);
        break;

    // handle someOtherData
    case 2:
      html += "<p>"+ data +"</p>";
      getMoreData(client, onNext);
      break;

    // handle moreData
    case 3:
      html += "<p>"+ data +"</p>";
      res.write(html);
      res.end();
      break;

    // general catch-all error-handler
    default:
      res.statusCode = 500;
      res.end(error.message + '\n' + error.stack);
    }
  };
  onNext();
});

1

मैंने हाल ही में सरल एब्स्ट्रेक्शन बनाया है जिसे वेट कहा जाता है । सिंक मोड में एस्किंक फ़ंक्शन को कॉल करने के लिए (फाइबर्स के आधार पर)। यह एक प्रारंभिक चरण में है लेकिन काम करता है। यह उस पर:

https://github.com/luciotato/waitfor

Wait.for का उपयोग करके , आप किसी भी मानक नोड्ज को async फ़ंक्शन कह सकते हैं, जैसे कि यह एक सिंक फ़ंक्शन था।

आपके कोड का इंतजार किया जा सकता है:

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

http.createServer(function(req, res) {
  wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning
}).listen(8080);


//in a fiber
function handleRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  var someData = wait.for(getSomeDate,client);
  html += "<p>"+ someData +"</p>";
  var someOtherData = wait.for(getSomeOtherDate,client);
  html += "<p>"+ someOtherData +"</p>";
  var moreData = wait.for(getMoreData,client);
  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
};

... या यदि आप कम वाचाल होना चाहते हैं (और त्रुटि पकड़ने को भी जोड़ें)

//in a fiber
function handleRequest(req, res) {
  try {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(
    "<h1>Demo page</h1>" 
    + "<p>"+ wait.for(getSomeDate,client) +"</p>"
    + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>"
    + "<p>"+ wait.for(getMoreData,client) +"</p>"
    );
    res.end();
  }
  catch(err) {
   res.end('error '+e.message); 
  }

};

सभी मामलों में, getSomeDate , getSomeOtherDate और getMoreData अंतिम पैरामीटर एक फ़ंक्शन कॉलबैक (गलत, डेटा) के साथ मानक async फ़ंक्शन होना चाहिए

जैसे की:

function getMoreData(client, callback){
  db.execute('select moredata from thedata where client_id=?',[client.id],
       ,function(err,data){
          if (err) callback(err);
          callback (null,data);
        });
}

1

इस समस्या को हल करने के लिए मैंने nodent ( https://npmjs.org/package/nodent ) लिखा था, जो कि जेएस के लिए अदृश्य रूप से पूर्व-प्रक्रिया है। आपका उदाहरण कोड बन जाएगा (async, वास्तव में - डॉक्स पढ़ें)।

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  someData <<= getSomeDate(client) ;

  html += "<p>"+ someData +"</p>";
  someOtherData <<= getSomeOtherDate(client) ;

  html += "<p>"+ someOtherData +"</p>";
  moreData <<= getMoreData(client) ;

  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
});

स्पष्ट रूप से, कई अन्य समाधान हैं, लेकिन प्री-प्रोसेसिंग में बहुत कम या कोई रन-टाइम ओवरहेड होने का लाभ है और स्रोत-मैप समर्थन के लिए धन्यवाद, यह डिबग करना भी आसान है।


0

मुझे भी यही समस्या थी। मैंने async फ़ंक्शंस को नोड करने के लिए प्रमुख कामों को देखा है, और वे आपके कोड को बनाने के लिए इतनी गैर-प्राकृतिक चेनिंग (आपको तीन या अधिक तरीकों का उपयोग करने के लिए कॉन्स आदि का उपयोग करने की आवश्यकता है) प्रस्तुत करते हैं।

मैंने कुछ हफ़्ते बिताए एक समाधान विकसित किया जो सरल और पढ़ने में आसान हो। कृपया, EnqJS को एक प्रयास दें । सभी राय की सराहना की जाएगी।

के बजाय:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

EnqJS के साथ:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";

  enq(function(){
    var self=this;
    getSomeDate(client, function(someData){
      html += "<p>"+ someData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getSomeOtherDate(client, function(someOtherData){ 
      html += "<p>"+ someOtherData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getMoreData(client, function(moreData) {
      html += "<p>"+ moreData +"</p>";
      self.return();
      res.write(html);
      res.end();
    });
  });
});

देखें कि कोड पहले की तुलना में बड़ा प्रतीत होता है। लेकिन यह पहले की तरह नेस्टेड नहीं है। अधिक प्राकृतिक दिखने के लिए, जंजीरों को इमीडेली कहा जाता है:

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...)

और यह कहने के लिए कि यह वापस लौटा, फ़ंक्शन के अंदर हम कहते हैं:

this.return(response)

0

मैं इसे एक सुंदर आदिम लेकिन प्रभावी तरीके से करता हूं। उदाहरण के लिए, मुझे इसके माता-पिता और बच्चों के साथ एक मॉडल प्राप्त करने की आवश्यकता है और मान लें कि मुझे उनके लिए अलग प्रश्न करने की आवश्यकता है:

var getWithParents = function(id, next) {
  var getChildren = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      },
      getParents = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      }
      getModel = function(id, next) {
        /*... code ... */
        if (model) {
          // return next callbacl
          return next.pop()(model, next);
        } else {
          // return last callback
          return next.shift()(null, next);
        }
      }

  return getModel(id, [getParents, getChildren, next]);
}

0

का प्रयोग करें रेशे https://github.com/laverdet/node-fibers यह (रोके बिना) तुल्यकालिक तरह एसिंक्रोनस कोड दिखता है बनाता है

मैं व्यक्तिगत रूप से इस छोटे रैपर का उपयोग करता हूं http://alexeypetrushin.github.com/synchronize मेरी परियोजना से कोड का नमूना (प्रत्येक विधि वास्तव में अतुल्यकालिक है, async फ़ाइल IO के साथ काम करना) मुझे भी कल्पना से डर लगता है कि कॉलबैक के साथ क्या गड़बड़ होगी या क्या होगा? async-control-flow हेल्पर लाइब्रेरी।

_update: (version, changesBasePath, changes, oldSite) ->
  @log 'updating...'
  @_updateIndex version, changes
  @_updateFiles version, changesBasePath, changes
  @_updateFilesIndexes version, changes
  configChanged = @_updateConfig version, changes
  @_updateModules version, changes, oldSite, configChanged
  @_saveIndex version
  @log "updated to #{version} version"

0

टास्क.जेएस आपको यह प्रदान करता है:

spawn(function*() {
    try {
        var [foo, bar] = yield join(read("foo.json"),
                                    read("bar.json")).timeout(1000);
        render(foo);
        render(bar);
    } catch (e) {
        console.log("read failed: " + e);
    }
});

इसके अलावा:

var foo, bar;
var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000);

var xhr1 = makeXHR("foo.json",
                   function(txt) { foo = txt; success() },
                   function(err) { failure() });
var xhr2 = makeXHR("bar.json",
                   function(txt) { bar = txt; success() },
                   function(e) { failure(e) });

function success() {
    if (typeof foo === "string" && typeof bar === "string") {
        cancelTimeout(tid);
        xhr1 = xhr2 = null;
        render(foo);
        render(bar);
    }
}

function failure(e) {
    xhr1 && xhr1.abort();
    xhr1 = null;
    xhr2 && xhr2.abort();
    xhr2 = null;
    console.log("read failed: " + e);
}

0

दूसरों के जवाब देने के बाद, आपने कहा कि आपकी समस्या स्थानीय चर थी। ऐसा करने का एक आसान तरीका ऐसा लगता है कि उन स्थानीय चर को समाहित करने के लिए एक बाहरी फ़ंक्शन लिखना है, फिर नामित आंतरिक फ़ंक्शन का एक गुच्छा उपयोग करें और उन्हें नाम से एक्सेस करें। इस तरह, आप कभी भी दो गहरे घोंसले बना सकते हैं, भले ही आपको एक साथ श्रृंखला के लिए कितने कार्यों की आवश्यकता हो।

यहाँ mysqlघोंसले के शिकार के साथ Node.js मॉड्यूल का उपयोग करने की मेरी नौसिखिया कोशिश है :

function with_connection(sql, bindings, cb) {
    pool.getConnection(function(err, conn) {
        if (err) {
            console.log("Error in with_connection (getConnection): " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, function(err, results) {
            if (err) {
                console.log("Error in with_connection (query): " + JSON.stringify(err));
                cb(true);
                return;
            }
            console.log("with_connection results: " + JSON.stringify(results));
            cb(false, results);
        });
    });
}

निम्नलिखित आंतरिक कार्यों का उपयोग करते हुए एक पुनर्लेखन है। बाहरी फ़ंक्शन with_connectionस्थानीय चर के लिए धारक के रूप में भी उपयोग किया जा सकता है। (यहाँ, मैं मानकों मिल गया है sql, bindings, cbएक समान तरीके से कि अधिनियम, लेकिन आप केवल में कुछ अतिरिक्त स्थानीय चर परिभाषित कर सकते हैं with_connection।)

function with_connection(sql, bindings, cb) {

    function getConnectionCb(err, conn) {
        if (err) {
            console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, queryCb);
    }

    function queryCb(err, results) {
        if (err) {
            console.log("Error in with_connection/queryCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        cb(false, results);
    }

    pool.getConnection(getConnectionCb);
}

मैं सोच रहा था कि शायद उदाहरण चर के साथ एक वस्तु बनाना संभव होगा, और स्थानीय चर के प्रतिस्थापन के रूप में इन उदाहरण चर का उपयोग करना संभव है। लेकिन अब मुझे पता चला है कि नेस्टेड फ़ंक्शंस और स्थानीय चर का उपयोग करने वाला उपरोक्त दृष्टिकोण सरल और समझने में आसान है। OO को अनलिंक करने में कुछ समय लगता है, ऐसा लगता है :-)

तो यहाँ एक वस्तु और उदाहरण चर के साथ मेरा पिछला संस्करण है।

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); });
}
DbConnection.prototype.query = function(err, results) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        self.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    self.cb(false, results);
}

function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); });
}

यह पता चला है कि bindकुछ लाभ के लिए इस्तेमाल किया जा सकता है। यह मुझे उन बदसूरत गुमनाम कार्यों से छुटकारा पाने की अनुमति देता है जो मैंने बनाई है जो कुछ भी नहीं करता है, सिवाय एक विधि कॉल के खुद को आगे बढ़ाने के लिए। मैं सीधे विधि पारित नहीं कर सकता क्योंकि यह गलत मूल्य के साथ शामिल था this। लेकिन bind, मैं thisजो चाहता हूं, उसका मूल्य निर्दिष्ट कर सकता हूं।

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var f = this.query.bind(this);
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, f);
}
DbConnection.prototype.query = function(err, results) {
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    this.cb(false, results);
}

// Get a connection from the pool, execute `sql` in it
// with the given `bindings`.  Invoke `cb(true)` on error,
// invoke `cb(false, results)` on success.  Here,
// `results` is an array of results from the query.
function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    var f = dbc.getConnection.bind(dbc);
    pool.getConnection(f);
}

बेशक, इनमें से कोई भी उचित नहीं है जेएस नोड के साथ कोडिंग के साथ - मैंने अभी इस पर कुछ घंटे बिताए हैं। लेकिन शायद थोड़ा चमकाने के साथ यह तकनीक मदद कर सकती है?


0

async.js इसके लिए अच्छा काम करता है। मुझे यह बहुत उपयोगी लेख मिला जो उदाहरणों के साथ async.js की आवश्यकता और उपयोग के बारे में बताता है: http://www.sebastianseilund.com/nodejs-async-in-ults


0

यदि आप "चरण" या "seq" का उपयोग नहीं करना चाहते हैं, तो कृपया "लाइन" आज़माएँ जो नेस्टेड async वापस करने के लिए एक सरल कार्य है।

https://github.com/kevin0571/node-line



0

तार का उपयोग करने से आपका कोड इस तरह दिखेगा:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});

    var l = new Wire();

    getSomeDate(client, l.branch('someData'));
    getSomeOtherDate(client, l.branch('someOtherData'));
    getMoreData(client, l.branch('moreData'));

    l.success(function(r) {
        res.write("<h1>Demo page</h1>"+
            "<p>"+ r['someData'] +"</p>"+
            "<p>"+ r['someOtherData'] +"</p>"+
            "<p>"+ r['moreData'] +"</p>");
        res.end();
    });
});

0

आपकी जानकारी के लिए Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase पर विचार करें

    const jj = आवश्यकता ('jazz.js');

    // अल्ट्रा-कमर्स स्टैक
    jj.script ([
        a => ProcessTaskOneCallbackAtEnd (a),
        b => ProcessTaskTwoCallbackAtEnd (b),
        c => ProcessTaskThreeCallbackAtEnd (c),
        d => ProcessTaskFourCallbackAtEnd (d),
        e => ProcessTaskFiveCallbackAtEnd (e),
    ]);

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