आप निरंतरता / कॉलबैक के साथ कोड को कैसे पठनीय रख सकते हैं?


11

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


मैं एक जावास्क्रिप्ट लाइब्रेरी का उपयोग कर रहा हूं जो बहुत सारे सामान को अतुल्यकालिक रूप से और भारी रूप से कॉलबैक पर निर्भर करती है। ऐसा लगता है कि एक सरल "लोड ए, लोड बी, ..." लिखना इस पद्धति का उपयोग करने के लिए काफी जटिल और कठिन हो जाता है।

मुझे एक (आकस्मिक) उदाहरण दें। मान लीजिए कि मैं दूरस्थ वेब सर्वर से छवियों का एक गुच्छा (अतुल्यकालिक रूप से) लोड करना चाहता हूं। C # / async में, मैं कुछ इस तरह लिखूंगा:

disableStartButton();

foreach (myData in myRepository) {
    var result = await LoadImageAsync("http://my/server/GetImage?" + myData.Id);
    if (result.Success) {
        myData.Image = result.Data;
    } else {
        write("error loading Image " + myData.Id);
        return;
    }
}

write("success");
enableStartButton();

कोड लेआउट "घटनाओं का प्रवाह" इस प्रकार है: पहला, प्रारंभ बटन अक्षम है, फिर चित्र लोड किए गए हैं ( awaitयह सुनिश्चित करता है कि यूआई उत्तरदायी रहता है) और फिर प्रारंभ बटन फिर से सक्षम है।

कॉलबैक का उपयोग करते हुए जावास्क्रिप्ट में, मैं इसके साथ आया:

disableStartButton();

var count = myRepository.length;

function loadImage(i) {
    if (i >= count) {
        write("success");
        enableStartButton();
        return;
    }

    myData = myRepository[i];
    LoadImageAsync("http://my/server/GetImage?" + myData.Id,
        function(success, data) { 
            if (success) {
                myData.Image = data;
            } else {
                write("error loading image " + myData.Id);
                return;
            }
            loadImage(i+1); 
        }
    );
}

loadImage(0);

मुझे लगता है कि कमियां स्पष्ट हैं: मुझे एक पुनरावर्ती कॉल में लूप को फिर से काम करना था, जिस कोड को अंत में निष्पादित किया जाना चाहिए वह फ़ंक्शन के बीच में कहीं है, डाउनलोड शुरू करने वाला कोड ( loadImage(0)) बहुत नीचे है, और यह आम तौर पर पढ़ने और पालन करने के लिए बहुत कठिन है। यह बदसूरत है और मुझे यह पसंद नहीं है।

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


क्या आपके "async" कॉल का कोई विशेष कारण क्रमिक रूप से होना है? क्या यह किसी अन्य कोड का सरलीकृत संस्करण है?
इजाकाता

@ इज़काता: कारण सिर्फ इतना है कि मैं रिमोट सर्वर के लिए अच्छा बनना चाहता था (= एक साथ सैकड़ों अनुरोधों के साथ बमबारी नहीं करता)। यह पत्थर में सेट की आवश्यकता नहीं है। हां, यह कोड का एक सरलीकृत संस्करण LoadImageAsyncहै, वास्तव Ext.Ajax.requestमें स्नेहा टच का एक कॉल है ।
हेनजी

1
अधिकांश ब्राउज़र आपको वैसे भी सर्वर को हथौड़ा करने की अनुमति नहीं देंगे - वे केवल अनुरोधों को कतारबद्ध करते हैं और पिछले पूर्ण होने पर अगले को शुरू करते हैं।
इज़्काता


परमेश्वर! यहाँ बहुत बुरी सलाह। कोई भी डिज़ाइन पैटर्न आपकी सहायता करने वाला नहीं है। Async.js में देखें , async.waterfallक्या आपका जवाब है।
सलमान वॉन अब्बास

जवाबों:


4

यह अत्यधिक संभावना नहीं है कि आप सादे js के साथ कॉलबैक के साथ काम करने में उसी स्तर की सहमति और स्पष्टता प्राप्त कर सकते हैं जो C # 5 के पास है। कंपाइलर आपके लिए वह सभी बॉयलरप्लेट लिखने में काम करता है, और जब तक जेएस रनटाइम्स नहीं करेगा, तब तक आपको यहां और यहां-वहां एक सामयिक कॉलबैक पास करना होगा।

हालाँकि, आप हमेशा कॉलबैक को रैखिक कोड की सादगी के स्तर तक नीचे नहीं लाना चाहते हैं - चारों ओर फ़ंक्शन को फेंकना बदसूरत नहीं होता है, इस तरह के कोड के साथ पूरी दुनिया काम कर रही है, और वे बिना asyncऔर बिना समझे चलते रहते हैं await

उदाहरण के लिए, उच्च-क्रम वाले फ़ंक्शंस का उपयोग करें (मेरा js थोड़ा कठोर हो सकता है):

// generic - this is a library function
function iterateAsync(iterator, action, onSuccess, onFailure) {
var item = iterator();
if(item == null) { // exit condition
    onSuccess();
    return;
}
action(item,
    function (success) {
        if(success)
            iterateAsync(iterator, action, onSuccess, onFailure);
        else
            onFailure();
    });
}


// calling code
var currentImage = 0;
var imageCount = 42;

// you know your library function expects an iterator with no params, 
// and an async action with the current item and its continuation as params
iterateAsync(
// this is your iterator
function () {   
    if(currentImage >= imageCount)
        return null;
    return "http://my/server/GetImage?" + (currentImage++);
},

// this is your action - coincidentally, no adaptor for the correct signature is necessary
LoadImageAsync,

// these are your outs
function () { console.log("All OK."); },
function () { console.log("FAILED!"); }
);

2

मुझे यह समझने के लिए थोड़ा समझ लिया कि आप इसे इस तरह क्यों कर रहे हैं, लेकिन मुझे लगता है कि आप जो चाहते हैं उसके करीब हो सकते हैं?

function loadImages() {
   var countRemainingToLoad = 0;
   var failures = 0;

   myRepository.each(function (myData) {
      countRemainingToLoad++;

      LoadImageAsync("http://my/server/GetImage?" + myData.Id,
        function(success, data) {
            if (success) {
                myData.Image = data;
            } else {
                write("error loading image " + myData.Id);
                failures++;
            }
            countRemainingToLoad--;
            if (countRemainingToLoad == 0 && failures == 0) {
                enableStartButton();
            }
        }
    );
}

disableStartButton();
loadImages();

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

संपादित करें : ध्यान दें कि यह आपके पास .each()उपलब्ध है, और myRepositoryयह एक सरणी है। सावधान रहें कि यदि आप उपलब्ध नहीं हैं तो लूप पुनरावृत्ति का उपयोग करें, अगर यह उपलब्ध नहीं है - यह कॉलबैक के लिए संपत्तियों को बंद करने का लाभ उठाता है। मुझे यकीन नहीं है कि आपके पास क्या उपलब्ध है, हालांकि, चूंकि LoadImageAsyncयह एक विशेष पुस्तकालय का हिस्सा लगता है - मुझे Google में कोई परिणाम नहीं दिखता है।


+1, मेरे पास .each()उपलब्ध है, और, अब जब आप इसका उल्लेख करते हैं, तो लोड को क्रमिक रूप से करने के लिए कड़ाई से आवश्यक नहीं है। मैं निश्चित रूप से आपके समाधान का प्रयास करूँगा। (हालांकि मैं विस्की के उत्तर को स्वीकार करूंगा, क्योंकि यह मूल, अधिक सामान्य प्रश्न के करीब है।)
हेनजी

@ हिनजी इस बात पर सहमत थे कि यह कितना अलग है, लेकिन (मुझे लगता है) यह भी एक सभ्य उदाहरण है कि कैसे अलग-अलग भाषाओं में एक ही चीज़ को संभालने के लिए अलग-अलग तरीके हैं। यदि किसी दूसरी भाषा में अनुवाद करते समय कुछ अजीब लगता है, तो शायद एक अलग प्रतिमान का उपयोग करके इसे करने का एक आसान तरीका है।
इज़काता

1

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

जो मैं जानता हूं, उसे संभालने के लिए कोई "अच्छी तरह से स्थापित" पैटर्न नहीं है। हालांकि, मैंने नेस्टेड कॉलबैक बुरे सपने से बचने के लिए दो तरह के तरीकों का इस्तेमाल किया है।

1 / अनाम कॉलबैक के बजाय नामित कार्यों का उपयोग करना

    function start() {
        mongo.findById( id, handleDatas );
    }

    function handleDatas( datas ) {
        // Handle the datas returned.
    }

इस तरह, आप दूसरे फ़ंक्शन में अनाम फ़ंक्शन का तर्क भेजकर नेस्टिंग से बचते हैं।

2 / प्रवाह प्रबंधन पुस्तकालय का उपयोग करना। मुझे स्टेप का उपयोग करना पसंद है , लेकिन यह केवल वरीयता का मामला है। यह एक लिंक्डइन का उपयोग करता है, वैसे।

    Step( {
        function start() {
            // the "this" magically sends to the next function.
            mongo.findById( this );
        },

        function handleDatas( el ) {
            // Handle the datas.
            // Another way to use it is by returning a value,
            // the value will be sent to the next function.
            // However, this is specific to Step, so look at
            // the documentation of the library you choose.
            return value;
        },

        function nextFunction( value ) {
            // Use the returned value from the preceding function
        }
    } );

जब मैं बहुत सारे नेस्टेड कॉलबैक का उपयोग करता हूं, तो मैं एक फ्लो मैनेजमेंट लाइब्रेरी का उपयोग करता हूं, क्योंकि जब कोड का उपयोग करना होता है तो यह बहुत अधिक पठनीय होता है।


0

सीधे शब्दों में कहें, तो जावास्क्रिप्ट में सिन्गनेटिक शुगर नहीं है await
लेकिन फ़ंक्शन के निचले भाग में "अंत" भाग को स्थानांतरित करना आसान है; और एक अनौपचारिक रूप से निष्पादित फ़ंक्शन के साथ, हम इसके संदर्भ की घोषणा करने से बच सकते हैं।

disableStartButton();

(function(i, count) {
    var loadImage = arguments.callee;
    myData = myRepository[i];

    LoadImageAsync("http://my/server/GetImage?" + myData.Id,
        function(success, data) { 
            if (!success) {
                write("error loading image " + myData.Id);

            } else {
                myData.Image = data;
                if (i < count) {
                    loadImage(i + 1, count);

                } else {
                    write("success");
                    enableStartButton();
                    return;

                }

            }

        }
    );
})(0, myRepository.length);

आप फ़ंक्शन को सफलता-कॉलबैक के रूप में "अंत" भाग भी पास कर सकते हैं।

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