Her geri arama tamamlandıktan sonra geri çağırma işlemi tamamlandıktan sonra geri arama


245

Başlıktan da anlaşılacağı gibi. Bunu nasıl yaparım?

whenAllDone()ForEach-döngü her öğe geçti ve bazı asenkron işleme yapıldıktan sonra aramak istiyorum .

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

Böyle çalışmasını sağlayabiliyor musunuz? ForEach için ikinci argüman tüm yinelemelerden geçtikten sonra çalışan bir geri çağırma işlevi olduğunda?

Beklenen çıktı:

3 done
1 done
2 done
All done!

13
Standart dizi forEachyöntemi donegeri arama parametresi ve allDonegeri arama olsaydı iyi olurdu !
Vanuan

22
Bu kadar basit bir şey JavaScript'te çok güreş gerektirir gerçek bir utanç.
Ali

Yanıtlar:


410

Array.forEach bu güzelliği sağlamıyorsa (oh, eğer yapacaktır), ancak istediğinizi başarmanın birkaç yolu vardır:

Basit bir sayaç kullanma

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 ve diğerleri sayesinde) Bu yaklaşım, "tamamlandı" geri çağrısını başlatmadan önce tüm öğelerin işlenmesini garanti eder. Geri aramada güncellenen bir sayaç kullanmanız gerekir. Dizin parametresinin değerine bağlı olarak aynı garanti sağlanmaz, çünkü eşzamansız işlemlerin dönüş sırası garanti edilmez.

ES6 Vaatlerini Kullanma

(eski tarayıcılar için bir vaat kütüphanesi kullanılabilir):

  1. Senkron yürütmeyi garanti eden tüm istekleri işleyin (örneğin 1 sonra 2 sonra 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. "Eşzamanlı" yürütme olmadan tüm eşzamansız istekleri işleyin (2, 1'den hızlı bitebilir)

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

Zaman uyumsuz kitaplık kullanma

Diğer asenkron kütüphaneler vardır zaman uyumsuz istediğini ifade mekanizmalar getirmesi en popüler olmak.

Düzenle

Sorunun gövdesi, daha önce eşzamanlı örnek kodu kaldırmak için düzenlendi, bu yüzden açıklığa kavuşturmak için cevabımı güncelledim. Orijinal örnek, eşzamansız davranışı modellemek için senkron benzeri kod kullandı, bu nedenle aşağıdakiler uygulandı:

array.forEacholan senkron ve böyledir res.writesadece foreach için çağrı sonra geri arama koymak, böylece:

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

  res.end();

31
Bununla birlikte, forEach içinde eşzamansız şeyler varsa (örneğin, bir dizi URL üzerinden dönüp üzerlerinde bir HTTP GET yapıyorsanız), res.end öğesinin en son çağrılacağının garantisi olmadığını unutmayın.
AlexMA


2
@Vanuan cevabınızı, oldukça önemli düzenlemenizi daha iyi eşleştirmek için güncelledim :)
Nick Tomlin

4
neden sadece değil if(index === array.length - 1)ve kaldıritemsProcessed
Amin Jafari

5
@AminJafari, eşzamansız çağrılar kaydedildikleri sırayla çözülemeyebilir (bir sunucuya çağrı yaptığınızı ve 2. çağrıda hafifçe durduğunu ancak son çağrı cezasını işlediğini varsayalım). Son eşzamansız çağrı öncekilerden önce çözülebilir. Bir karşı mutasyon bu duruma karşı koruma sağlar, çünkü tüm geri çağrılar, hangi sırayla çözüldüklerine bakılmaksızın tetiklenmelidir .
Nick Tomlin

25

Eşzamansız işlevlerle karşılaşırsanız ve kodu yürütmeden önce görevini bitirdiğinden emin olmak istiyorsanız, her zaman geri arama özelliğini kullanabiliriz.

Örneğin:

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

Not: functionAfterForEachher görev tamamlandıktan sonra yürütülecek işlevdir. asynchronousforeach içinde yürütülen eşzamansız işlevdir.


9
Bu, zaman uyumsuz isteklerin yürütme sırası gerekli olmadığından çalışmaz. Son zaman uyumsuz istek diğerlerinden önce sona erebilir ve tüm istekler yerine getirilmeden önce functionAfterForEach () öğesini yürütebilir.
Rémy DAVID

@ RémyDAVID evet, yürütme sırası ile ilgili bir noktanız var ya da sürecin ne kadar süre tamamlandığını söyleyeyim, javascript tek iş parçacıklı olduğundan, sonunda çalışır. Ve kanıt, bu cevabın aldığı oydu.
Emil Reña Enriquez

1
Neden bu kadar çok oy verdiğinizden emin değilim, ama Rémi doğru. Kodunuz hiç çalışmaz, çünkü eşzamansız, isteğin herhangi bir zamanda geri dönebileceği anlamına gelir. JavaScript birden çok iş parçacığı olmamasına rağmen, tarayıcınız. Yoğun olarak ekleyebilirim. Bu nedenle, bir sunucudan yanıt alınmasına bağlı olarak, herhangi bir zamanda geri aramalarınızdan herhangi birini çağırabilir ...
Alexis Wilke

2
evet, bu cevap tamamen yanlış. 10 indirmeyi paralel olarak çalıştırırsam, son indirmenin geri kalanın önünde bitmesi ve böylece yürütmenin sona erdirilmesi garanti edilir.
knrdk

Tamamlanmış eşzamansız görevlerin sayısını artırmak ve dizin yerine dizinin uzunluğuyla eşleştirmek için bir sayaç kullanmanızı öneririm. Yukarı oyların sayısının, cevabın doğruluğunun kanıtı ile ilgisi yoktur.
Alex

17

Umarım bu sorununuzu düzeltir, ben genellikle zaman uyumsuz görevleri ile forEach yürütmek gerektiğinde bu ile çalışır.

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

ile

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

Açısal 9 kodumda da benzer bir sorun yaşıyordum ve bu cevap benim için hile yaptı. @Emil Reña Enriquez yanıtı da benim için çalıştı ama bu sorunun daha doğru ve basit bir cevap olduğunu düşünüyorum.
omostan

17

Asenkron davaya kaç tane yanlış cevap verilmesi garip ! Basitçe, kontrol dizininin beklenen davranış sağlamadığı gösterilebilir:

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

çıktı:

4000 started
2000 started
1: 2000
0: 4000

Kontrol edersek index === array.length - 1, ilk yineleme tamamlandığında geri arama çağrılırken, ilk öğe hala beklemede olur!

Async gibi harici kütüphaneleri kullanmadan bu sorunu çözmek için, en iyi bahsiniz her yinelemeden sonra liste uzunluğunu ve azalmayı kaydetmek olduğunu düşünüyorum. Sadece bir iplik olduğundan, yarış durumu şansı olmadığından eminiz.

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
Muhtemelen tek çözüm budur. Zaman uyumsuz kütüphane de sayaç kullanıyor mu?
Vanuan

1
Diğer çözümler işi yapmasına rağmen, bu en zorlayıcıdır çünkü zincirleme veya karmaşıklık gerektirmez. KISS
azatar

Lütfen dizi uzunluğunun sıfır olduğu durumu da göz önünde bulundurun, bu durumda geri arama asla çağrılmaz
Saeed Ir

6

ES2018 ile zaman uyumsuz yineleyicileri kullanabilirsiniz:

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
Düğüm v10
Matt Swezey'de

2

Sözsüz çözümüm (bu, her eylemin bir sonraki adım başlamadan bitmesini sağlar):

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
Çalışmaz, çünkü eğer foreach içinde asenkron işlem yapacaksanız.
Sudhanshu Gaur


0

Çözümüm:

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

Misal:

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

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

// out: a b c done

Çözüm yenilikçi ama bir hata geliyor - "görev bir işlev değil"
Genius

0

Çözmek için kolay bir yol deniyorum, sizinle paylaşın:

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

requestjs Düğümü'ndeki mssql Kütüphanesinin İşlevidir. Bu, istediğiniz her işlevi veya Kodu değiştirebilir. İyi şanslar


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

Bir listeyi yinelemek için geri aramaya ihtiyacınız olmamalıdır. end()Çağrıyı döngüden sonra ekleyin .

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

3
Hayır. OP, her bir yineleme için eşzamansız mantığın yürütüleceğini vurguladı. res.writeeşzamansız bir işlem DEĞİLDİR, bu nedenle kodunuz çalışmaz.
Jim

-2

Basit bir çözüm takip etmek gibi olurdu

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

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

3
Sorunun tamamı olan eşzamansız kod için çalışmaz.
grg

-3

SetInterval'a tam yineleme sayısını kontrol etmek için garanti verir. Ancak kapsamı aşırı yükleyip yüklemeyeceğinden emin değilim ama kullanıyorum ve tek görünüyor

_.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);

Bu mantıksal olarak basit görünüyor
Zeal Murapa
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.