Eşzamansız geri arama işlevlerini nasıl bekleyebilirim?


95

JavaScript'te buna benzer bir kodum var:

forloop {
    //async call, returns an array to its callback
}

Bu eşzamansız aramaların TÜMÜ yapıldıktan sonra, tüm diziler üzerinde min'i hesaplamak istiyorum.

Hepsini nasıl bekleyebilirim?

Şu anda tek fikrim bitmiş olarak adlandırılan bir mantık dizisine sahip olmak ve i'inci geri çağırma işlevinde done [i] 'yi true olarak ayarlamak, sonra while demek (hepsi bitmedi) {}

edit: Sanırım bir olası, ancak çirkin çözüm, her geri aramada bitmiş diziyi düzenlemek, ardından yapılan diğer tüm işlemler her geri aramadan ayarlanmışsa bir yöntemi çağırmak olacaktır, bu nedenle tamamlanması gereken son geri arama, devam eden yöntemi çağıracaktır.

Şimdiden teşekkürler.


1
Zaman uyumsuzda, bir Ajax isteğinin tamamlanmasını mı beklemek istiyorsunuz?
Peter Aron Zentai

7
Not, while (not all are done) { }işe yaramaz. Beklerken meşgulken, geri aramalarınızın hiçbiri çalışmaz.
cHao

Evet. Geri arama yöntemlerini çalıştırması için harici bir API'ye zaman uyumsuz bir çağrı bekliyorum. Evet cHao, bunu fark ettim, bu yüzden burada yardım istiyorum: D
codersarepeople

Bunu deneyebilirsiniz: github.com/caolan/async Çok güzel eşzamansız yardımcı program işlevleri seti.
Paul Greyson

Yanıtlar:


192

Kodunuz konusunda çok spesifik değildiniz, bu yüzden bir senaryo oluşturacağım. Diyelim ki 10 ajax çağrınız var ve bu 10 ajax çağrısının sonuçlarını toplamak istiyorsunuz ve sonra hepsi tamamlandığında bir şeyler yapmak istiyorsunuz. Verileri bir dizide toplayarak ve sonuncunun ne zaman bittiğini takip ederek bunu şu şekilde yapabilirsiniz:

Manuel Sayaç

var ajaxCallsRemaining = 10;
var returnedData = [];

for (var i = 0; i < 10; i++) {
    doAjax(whatever, function(response) {
        // success handler from the ajax call

        // save response
        returnedData.push(response);

        // see if we're done with the last ajax call
        --ajaxCallsRemaining;
        if (ajaxCallsRemaining <= 0) {
            // all data is here now
            // look through the returnedData and do whatever processing 
            // you want on it right here
        }
    });
}

Not: Hata işleme burada önemlidir (ajax çağrılarınızı nasıl yaptığınızla ilgili olduğu için gösterilmemiştir). Bir ajax çağrısı ya bir hata ile ya da uzun bir süre takılı kaldığında ya da uzun bir süre sonra zaman aşımına uğradığında, bir ajax çağrısı asla tamamlanmadığında, durumu nasıl ele alacağınızı düşünmek isteyeceksiniz.


jQuery Sözleri

2014'teki cevabıma ekliyorum. Bugünlerde, jQuery $.ajax()zaten bir söz verdiğinden ve $.when()bir grup sözün tamamının çözüldüğünde size haber vereceğinden ve sizin için geri dönüş sonuçlarını toplayacağından bu tür bir sorunu çözmek için sözler sıklıkla kullanılmaktadır :

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push($.ajax(...));
}
$.when.apply($, promises).then(function() {
    // returned data is in arguments[0][0], arguments[1][0], ... arguments[9][0]
    // you can process it here
}, function() {
    // error occurred
});

ES6 Standart Vaatleri

Kba'nın cevabında belirtildiği gibi : yerleşik vaatleri olan bir ortamınız varsa (modern tarayıcı veya node.js veya babeljs transpile veya bir vaat çoklu doldurma kullanarak), o zaman ES6 tarafından belirtilen vaatleri kullanabilirsiniz. Tarayıcı desteği için bu tabloya bakın . Sözler, IE hariç hemen hemen tüm mevcut tarayıcılarda desteklenmektedir.

doAjax()Bir söz verirse , şunu yapabilirsiniz:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
    // returned data is in arguments[0], arguments[1], ... arguments[n]
    // you can process it here
}, function(err) {
    // error occurred
});

Söz vermeyen zaman uyumsuz bir işlemi bir söz veren bir işlemde yapmanız gerekiyorsa, bunu şu şekilde "söz verebilirsiniz":

function doAjax(...) {
    return new Promise(function(resolve, reject) {
        someAsyncOperation(..., function(err, result) {
            if (err) return reject(err);
            resolve(result);
        });
    });
}

Ve sonra yukarıdaki kalıbı kullanın:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
    // returned data is in arguments[0], arguments[1], ... arguments[n]
    // you can process it here
}, function(err) {
    // error occurred
});

Bluebird Sözleri

Bluebird vaat kitaplığı gibi daha zengin özelliklere sahip bir kitaplık kullanıyorsanız , bunu kolaylaştırmak için bazı ek işlevler yerleşik olarak bulunur:

 var doAjax = Promise.promisify(someAsync);
 var someData = [...]
 Promise.map(someData, doAjax).then(function(results) {
     // all ajax results here
 }, function(err) {
     // some error here
 });

4
@kba - Tüm teknikler hala uygulanabilir olduğu için, özellikle de Ajax için jQuery kullanıyorsanız, bu cevabı tam olarak modası geçmiş olarak adlandırmazdım. Ancak, yerel vaatleri içerecek şekilde birkaç şekilde güncelledim.
jfriend00

Günümüzde jquery'ye bile ihtiyaç duymayan çok daha temiz bir çözüm var. Bunu FetchAPI ve Promises ile yapıyorum
philx_x

@philx_x - IE ve Safari desteği konusunda ne yapıyorsunuz?
jfriend00

@ jfriend00 github bir polyfill github.com/github/fetch yaptı . Ya da babel'in henüz getirmeyi destekleyip desteklemediğinden emin değilim. babeljs.io
philx_x

@philx_x - Öyle sanıyordum. Günümüzde getirmeyi kullanmak için bir çoklu doldurma kitaplığına ihtiyacınız var. Ajax kitaplığından kaçınmakla ilgili yorumunuzdan biraz hava alır. Getirme güzeldir, ancak onu polyfill olmadan kullanabilmekten yıllar sonra. Henüz tüm tarayıcıların en son sürümünde bile değil. Yap, cevabımdaki hiçbir şeyi değiştirmez. doAjax()Seçeneklerden biri olarak bir söz veren bir sözüm vardı . İle aynı şey fetch().
jfriend00

17

2015 dan kontrol etme: Artık sahip yerli sözler içinde en son tarayıcısı (Kenar 12, Firefox 40, Chrome 43, Safari 8, Opera 32 ve Android tarayıcı 4.4.4 ve iOS Safari 8.4, ancak Internet Explorer, Opera Mini ve daha eski sürümler Android).

10 eşzamansız eylem gerçekleştirmek ve hepsi bittiğinde bildirim almak istiyorsak Promise.all, herhangi bir harici kitaplık olmadan yerel olanı kullanabiliriz :

function asyncAction(i) {
    return new Promise(function(resolve, reject) {
        var result = calculateResult();
        if (result.hasError()) {
            return reject(result.error);
        }
        return resolve(result);
    });
}

var promises = [];
for (var i=0; i < 10; i++) {
    promises.push(asyncAction(i));
}

Promise.all(promises).then(function AcceptHandler(results) {
    handleResults(results),
}, function ErrorHandler(error) {
    handleError(error);
});

2
Promises.all()olmalıdır Promise.all().
jfriend00

1
Cevabınız ayrıca , IE'nin mevcut sürümlerini içermeyen hangi tarayıcıları kullanabileceğinizi belirtmelidir Promise.all().
jfriend00

10

JQuery'nin Deferred nesnesini when yöntemiyle birlikte kullanabilirsiniz .

deferredArray = [];
forloop {
    deferred = new $.Deferred();
    ajaxCall(function() {
      deferred.resolve();
    }
    deferredArray.push(deferred);
}

$.when(deferredArray, function() {
  //this code is called after all the ajax calls are done
});

7
Soru etiketlenmedi, jQuerybu da genellikle OP'nin jQuery cevabı istemediği anlamına gelir.
jfriend00

8
@ jfriend00 JQuery'de zaten oluşturulmuş haldeyken tekerleği yeniden icat etmek istemedim
Paul

4
@Paul, tekerleği yeniden icat etmek yerine, basit bir şey yapmak için 40kb gereksiz (ertelenmiş)
Raynos

2
Ancak herkes jQuery'yi kullanamaz veya kullanmak istemez ve burada SO'daki gelenek, sorunuzu jQuery ile etiketleyip etiketlemeseniz de bunu belirtmenizdir.
jfriend00

4
$ .When çağrı bu örnek yanlıştır. Bir dizi ertelenmiş / vaat beklemek için $ .when.apply ($, promises) .then (function () {/ * do stuff * /}) kullanmanız gerekir.
danw

9

Bunu şu şekilde taklit edebilirsiniz:

  countDownLatch = {
     count: 0,
     check: function() {
         this.count--;
         if (this.count == 0) this.calculate();
     },
     calculate: function() {...}
  };

her zaman uyumsuz çağrı bunu yapar:

countDownLatch.count++;

her eşzamansız aramada yöntemin sonuna şu satırı eklersiniz:

countDownLatch.check();

Diğer bir deyişle, geri sayım mandalı işlevini taklit edersiniz.


Tüm kullanım durumlarının% 99'unda bir Promise gitmenin yoludur, ancak bu cevabı seviyorum çünkü bir Promise polyfill'in onu kullanan JS'den daha büyük olduğu durumlarda Async kodunu yönetmek için bir yöntem gösteriyor!
Sukima

6

Bence bu en düzgün yol.

Promise.all

FetchAPI

(Array.map bazı nedenlerden dolayı .then işlevlerinin içinde çalışmıyor. Ama bir .forEach ve [] .concat () veya benzer bir şey kullanabilirsiniz)

Promise.all([
  fetch('/user/4'),
  fetch('/user/5'),
  fetch('/user/6'),
  fetch('/user/7'),
  fetch('/user/8')
]).then(responses => {
  return responses.map(response => {response.json()})
}).then((values) => {
  console.log(values);
})

1
Bunun return responses.map(response => { return response.json(); })veya olması gerektiğini düşünüyorum return responses.map(response => response.json()).

1

Gibi bir kontrol akışı kitaplığı kullanın after

after.map(array, function (value, done) {
    // do something async
    setTimeout(function () {
        // do something with the value
        done(null, value * 2)
    }, 10)
}, function (err, mappedArray) {
    // all done, continue here
    console.log(mappedArray)
})
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.