सभी अतुल्यकालिक forAach कॉलबैक के पूरा होने के बाद कॉलबैक


245

जैसा कि शीर्षक से पता चलता है। मैं यह कैसे करु?

मैं चाहता हूं कि whenAllDone()प्रत्येक तत्व के माध्यम से फॉर्च-लूप जाने के बाद और कुछ अतुल्यकालिक प्रसंस्करण किया जाए।

[1, 2, 3].forEach(
  function(item, index, array, done) {
     asyncFunction(item, function itemDone() {
       console.log(item + " done");
       done();
     });
  }, function allDone() {
     console.log("All done");
     whenAllDone();
  }
);

इस तरह से काम करना संभव है? जब प्रत्येक के लिए दूसरा तर्क एक कॉलबैक फ़ंक्शन है जो सभी पुनरावृत्तियों के माध्यम से एक बार चलता है?

अपेक्षित उत्पादन:

3 done
1 done
2 done
All done!

13
यह अच्छा होगा यदि मानक सरणी forEachविधि में doneकॉलबैक पैरामीटर और allDoneकॉलबैक था!
वानुआन

22
यह वास्तव में कुछ शर्म की बात है कि जावास्क्रिप्ट में बहुत अधिक कुश्ती की आवश्यकता है।
अली

जवाबों:


410

Array.forEach यह नॉट्टी प्रदान नहीं करता है (यदि ऐसा है तो ओह) लेकिन आप जो चाहते हैं उसे पूरा करने के कई तरीके हैं:

एक साधारण काउंटर का उपयोग करना

function callback () { console.log('all done'); }

var itemsProcessed = 0;

[1, 2, 3].forEach((item, index, array) => {
  asyncFunction(item, () => {
    itemsProcessed++;
    if(itemsProcessed === array.length) {
      callback();
    }
  });
});

(@vanuan और अन्य के लिए धन्यवाद) यह दृष्टिकोण गारंटी देता है कि सभी आइटम "किए गए" कॉलबैक को लागू करने से पहले संसाधित होते हैं। आपको कॉलबैक में अपडेट होने वाले काउंटर का उपयोग करने की आवश्यकता है। सूचकांक पैरामीटर के मूल्य के आधार पर एक ही गारंटी प्रदान नहीं करता है, क्योंकि अतुल्यकालिक संचालन की वापसी के आदेश की गारंटी नहीं है।

ईएस 6 प्रॉमिस का उपयोग करना

(एक वादा पुस्तकालय पुराने ब्राउज़रों के लिए इस्तेमाल किया जा सकता है):

  1. तुल्यकालिक निष्पादन की गारंटी देने वाले सभी अनुरोधों को संसाधित करें (जैसे 1 तब 2 फिर 3)

    function asyncFunction (item, cb) {
      setTimeout(() => {
        console.log('done with', item);
        cb();
      }, 100);
    }
    
    let requests = [1, 2, 3].reduce((promiseChain, item) => {
        return promiseChain.then(() => new Promise((resolve) => {
          asyncFunction(item, resolve);
        }));
    }, Promise.resolve());
    
    requests.then(() => console.log('done'))
  2. "तुल्यकालिक" निष्पादन के बिना सभी async अनुरोधों को संसाधित करें (2 1 से अधिक तेजी से समाप्त हो सकता है)

    let requests = [1,2,3].map((item) => {
        return new Promise((resolve) => {
          asyncFunction(item, resolve);
        });
    })
    
    Promise.all(requests).then(() => console.log('done'));

एक Async लाइब्रेरी का उपयोग करना

अन्य अतुल्यकालिक पुस्तकालय हैं, async सबसे लोकप्रिय है, जो आप चाहते हैं व्यक्त करने के लिए तंत्र प्रदान करते हैं।

संपादित करें

प्रश्न का मुख्य भाग पहले सिंक्रोनस उदाहरण कोड को हटाने के लिए संपादित किया गया है, इसलिए मैंने स्पष्टीकरण देने के लिए अपना उत्तर अपडेट कर दिया है। मूल एसिंक्रोनस व्यवहार को कोड करने के लिए कोड जैसे मूल उदाहरण का उपयोग किया जाता है, इसलिए निम्नलिखित लागू होता है:

array.forEachहै तुल्यकालिक और इतने है res.write, तो आप बस foreach के लिए अपने कॉल के बाद अपने कॉलबैक रख सकते हैं:

  posts.foreach(function(v, i) {
    res.write(v + ". index " + i);
  });

  res.end();

31
ध्यान दें, हालांकि, अगर forEach के अंदर अतुल्यकालिक सामान है (जैसे कि आप URL की एक सरणी के माध्यम से लूप कर रहे हैं और उन पर HTTP GET कर रहे हैं), तो कोई गारंटी नहीं है कि res.end को अंतिम कहा जाएगा।
एलेक्सा

एक कॉल में आग लगाने के बाद कॉलबैक में आग लगाने के लिए आप
एस्क्

2
@Vanuan मैं अपने जवाब को बेहतर तरीके से संपादित करने के बजाय अपने अपडेट को संपादित करने के लिए अद्यतन किया है :)
निक टॉमलिन

4
सिर्फ if(index === array.length - 1)और सिर्फ क्यों नहींitemsProcessed
अमीन जाफरी

5
@AminJafari क्योंकि एसिंक्रोनस कॉल सटीक क्रम में हल नहीं हो सकते हैं जो वे पंजीकृत हैं (कहते हैं कि आप एक सर्वर पर कॉल कर रहे हैं और यह 2 कॉल पर थोड़ा रुकता है लेकिन अंतिम कॉल ठीक करता है)। पिछले अतुल्यकालिक कॉल पिछले वाले से पहले हल कर सकता है। इसके खिलाफ एक काउंटर गार्ड को म्यूट करना क्योंकि सभी कॉलबैक को उस आदेश की परवाह किए बिना आग लगाना चाहिए जिसमें वे हल करते हैं।
निक टोमलिन

25

यदि आप अतुल्यकालिक कार्यों का सामना करते हैं, और आप यह सुनिश्चित करना चाहते हैं कि कोड को निष्पादित करने से पहले यह अपना कार्य पूरा कर ले, तो हम हमेशा कॉलबैक क्षमता का उपयोग कर सकते हैं।

उदाहरण के लिए:

var ctr = 0;
posts.forEach(function(element, index, array){
    asynchronous(function(data){
         ctr++; 
         if (ctr === array.length) {
             functionAfterForEach();
         }
    })
});

नोट: functionAfterForEachकार्य के समाप्त होने के बाद निष्पादित किया जाने वाला कार्य है। asynchronousएक अतुल्यकालिक समारोह है जिसे फार्च के अंदर निष्पादित किया जाता है।


9
यह काम नहीं करेगा क्योंकि अतुल्यकालिक अनुरोधों के निष्पादन का आदेश चेतावनी नहीं है। अंतिम async अनुरोध दूसरों से पहले समाप्त हो सकता है और सभी अनुरोधों को पूरा करने से पहले functionAfterForEach () को निष्पादित कर सकता है।
रेमी डेविड

@ RémyDAVID हां, आपके पास निष्पादन के आदेश के बारे में एक बिंदु है या मैं कहूंगा कि प्रक्रिया कितने समय में समाप्त हो रही है, लेकिन जावास्क्रिप्ट को सिंगल थ्रेडेड किया जा रहा है इसलिए यह अंततः काम करता है। और प्रमाण इस उत्तर को प्राप्त होता है।
एमिल रेना एनरिकेज़

1
मुझे बहुत यकीन नहीं है कि आपके पास इतने सारे अपवोट्स हैं, लेकिन रेमी सही है। आपका कोड बिलकुल भी काम नहीं करेगा क्योंकि एसिंक्रोनस का मतलब है कि कोई भी अनुरोध किसी भी समय वापस आ सकता है। हालाँकि जावास्क्रिप्ट मल्टीथ्रेड्स नहीं है, आपका ब्राउज़र है। भारी, मैं जोड़ सकता है। यह किसी भी समय किसी भी क्रम में आपके कॉलबैक में से किसी को भी कॉल कर सकता है, जब किसी सर्वर से जवाब मिलने के आधार पर ...
एलेक्सिस विल्के

2
हाँ, यह उत्तर पूरी तरह से गलत है। यदि मैं समानांतर में 10 डाउनलोड चलाता हूं, तो यह सभी की गारंटी है कि अंतिम डाउनलोड बाकी के आगे खत्म हो जाता है और इस तरह निष्पादन समाप्त हो जाता है।
knrdk

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

17

आशा है कि यह आपकी समस्या को ठीक करेगा, मैं आमतौर पर इसके साथ काम करता हूं जब मुझे अंदर के अतुल्यकालिक कार्यों के साथ forEach निष्पादित करने की आवश्यकता होती है।

foo = [a,b,c,d];
waiting = foo.length;
foo.forEach(function(entry){
      doAsynchronousFunction(entry,finish) //call finish after each entry
}
function finish(){
      waiting--;
      if (waiting==0) {
          //do your Job intended to be done after forEach is completed
      } 
}

साथ में

function doAsynchronousFunction(entry,callback){
       //asynchronousjob with entry
       callback();
}

मैं अपने कोणीय 9 कोड में इसी तरह का मुद्दा रख रहा था और इस जवाब ने मेरे लिए चाल चली। हालाँकि @Emil Reña Enriquez उत्तर ने भी मेरे लिए काम किया लेकिन मुझे यह इस समस्या के लिए अधिक सटीक और सरल उत्तर लगता है।
ओमोस्तन

17

यह विषम है कि अतुल्यकालिक मामले को कितने गलत जवाब दिए गए हैं ! यह बस दिखाया जा सकता है कि चेकिंग इंडेक्स अपेक्षित व्यवहार प्रदान नहीं करता है:

// INCORRECT
var list = [4000, 2000];
list.forEach(function(l, index) {
    console.log(l + ' started ...');
    setTimeout(function() {
        console.log(index + ': ' + l);
    }, l);
});

उत्पादन:

4000 started
2000 started
1: 2000
0: 4000

अगर हम जांच करते हैं index === array.length - 1, तो कॉलबैक पहले पुनरावृत्ति के पूरा होने पर बुलाया जाएगा, जबकि पहले तत्व अभी भी लंबित है!

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

var list = [4000, 2000];
var counter = list.length;
list.forEach(function(l, index) {
    console.log(l + ' started ...');
    setTimeout(function() {
        console.log(index + ': ' + l);
        counter -= 1;
        if ( counter === 0)
            // call your callback here
    }, l);
});

1
शायद यही एकमात्र उपाय है। क्या Async लाइब्रेरी काउंटरों का भी उपयोग करती है?
वानुअन

1
हालांकि अन्य समाधान काम करते हैं, यह सबसे अधिक सम्मोहक है क्योंकि इसे चैनिंग या जोड़ा जटिलता की आवश्यकता नहीं है। KISS
azatar

कृपया उस स्थिति पर भी विचार करें जब सरणी की लंबाई शून्य है, इस मामले में, कॉलबैक कभी नहीं कहा जाएगा
सईद इर

6

ES2018 के साथ आप async पुनरावृत्तियों का उपयोग कर सकते हैं:

const asyncFunction = a => fetch(a);
const itemDone = a => console.log(a);

async function example() {
  const arrayOfFetchPromises = [1, 2, 3].map(asyncFunction);

  for await (const item of arrayOfFetchPromises) {
    itemDone(item);
  }

  console.log('All done');
}

1
नोड V10 में अवेलेबे
मैट स्वेज़े

2

प्रॉमिस के बिना मेरा समाधान (यह सुनिश्चित करता है कि हर क्रिया अगले एक शुरू होने से पहले समाप्त हो जाए):

Array.prototype.forEachAsync = function (callback, end) {
        var self = this;
    
        function task(index) {
            var x = self[index];
            if (index >= self.length) {
                end()
            }
            else {
                callback(self[index], index, self, function () {
                    task(index + 1);
                });
            }
        }
    
        task(0);
    };
    
    
    var i = 0;
    var myArray = Array.apply(null, Array(10)).map(function(item) { return i++; });
    console.log(JSON.stringify(myArray));
    myArray.forEachAsync(function(item, index, arr, next){
      setTimeout(function(){
        $(".toto").append("<div>item index " + item + " done</div>");
        console.log("action " + item + " done");
        next();
      }, 300);
    }, function(){
        $(".toto").append("<div>ALL ACTIONS ARE DONE</div>");
        console.log("ALL ACTIONS ARE DONE");
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="toto">

</div>


1
 var counter = 0;
 var listArray = [0, 1, 2, 3, 4];
 function callBack() {
     if (listArray.length === counter) {
         console.log('All Done')
     }
 };
 listArray.forEach(function(element){
     console.log(element);
     counter = counter + 1;
     callBack();
 });

1
यह काम नहीं करेगा क्योंकि यदि आपके पास foreach के अंदर async ऑपरेशन होगा।
सुधांशु गौर


0

मेरा समाधान:

//Object forEachDone

Object.defineProperty(Array.prototype, "forEachDone", {
    enumerable: false,
    value: function(task, cb){
        var counter = 0;
        this.forEach(function(item, index, array){
            task(item, index, array);
            if(array.length === ++counter){
                if(cb) cb();
            }
        });
    }
});


//Array forEachDone

Object.defineProperty(Object.prototype, "forEachDone", {
    enumerable: false,
    value: function(task, cb){
        var obj = this;
        var counter = 0;
        Object.keys(obj).forEach(function(key, index, array){
            task(obj[key], key, obj);
            if(array.length === ++counter){
                if(cb) cb();
            }
        });
    }
});

उदाहरण:

var arr = ['a', 'b', 'c'];

arr.forEachDone(function(item){
    console.log(item);
}, function(){
   console.log('done');
});

// out: a b c done

समाधान अभिनव है लेकिन एक त्रुटि आ रही है - "कार्य एक कार्य नहीं है"
जीनियस

0

मैं इसे हल करने के लिए आसान तरीका आज़माता हूं, इसे आपके साथ साझा करूंगा:

let counter = 0;
            arr.forEach(async (item, index) => {
                await request.query(item, (err, recordset) => {
                    if (err) console.log(err);

                    //do Somthings

                    counter++;
                    if(counter == tableCmd.length){
                        sql.close();
                        callback();
                    }
                });

requestनोड्स में mssql लाइब्रेरी का कार्य है। यह प्रत्येक फ़ंक्शन या कोड यू को बदल सकता है। शुभ लाभ


0
var i=0;
const waitFor = (ms) => 
{ 
  new Promise((r) => 
  {
   setTimeout(function () {
   console.log('timeout completed: ',ms,' : ',i); 
     i++;
     if(i==data.length){
      console.log('Done')  
    }
  }, ms); 
 })
}
var data=[1000, 200, 500];
data.forEach((num) => {
  waitFor(num)
})

-2

आपको किसी सूची के माध्यम से पुनरावृत्ति के लिए कॉलबैक की आवश्यकता नहीं होनी चाहिए। बस end()लूप के बाद कॉल जोड़ें ।

posts.forEach(function(v, i){
   res.write(v + ". Index " + i);
});
res.end();

3
नहीं। ओपी ने जोर दिया कि अतुल्यकालिक तर्क प्रत्येक पुनरावृत्ति के लिए निष्पादित होगा। res.writeएक अतुल्यकालिक ऑपरेशन नहीं है, इसलिए आपका कोड काम नहीं करेगा।
जिम जी।

-2

एक सरल समाधान अनुसरण की तरह होगा

function callback(){console.log("i am done");}

["a", "b", "c"].forEach(function(item, index, array){
    //code here
    if(i == array.length -1)
    callback()
}

3
एसिंक्रोनस कोड के लिए काम नहीं करता है जो प्रश्न का संपूर्ण आधार है।
जीआर

-3

कैसे के बारे में setInterval, पूर्ण पुनरावृत्ति गिनती की जांच करने के लिए, गारंटी लाता है। यकीन नहीं है कि अगर यह गुंजाइश को अधिभार नहीं डालेगा, लेकिन मैं इसका उपयोग करता हूं और ऐसा लगता है

_.forEach(actual_JSON, function (key, value) {

     // run any action and push with each iteration 

     array.push(response.id)

});


setInterval(function(){

    if(array.length > 300) {

        callback()

    }

}, 100);

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