Bagaimana saya bisa menunggu sekumpulan fungsi panggilan balik asinkron?

95

Saya memiliki kode yang terlihat seperti ini di javascript:

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

Setelah SEMUA panggilan asinkron selesai, saya ingin menghitung min di atas semua array.

Bagaimana saya bisa menunggu semuanya?

Satu-satunya ide saya saat ini adalah memiliki larik boolean yang disebut selesai, dan menyetel selesai [i] menjadi true dalam fungsi panggilan balik ke-i, lalu mengatakan while (tidak semua selesai) {}

edit: Saya kira satu solusi yang mungkin, tetapi jelek, adalah mengedit array done di setiap callback, lalu memanggil metode jika semua selesai lainnya disetel dari setiap callback, sehingga callback terakhir yang diselesaikan akan memanggil metode berkelanjutan.

Terima kasih sebelumnya.

codersarepeople
sumber
1
Pada asinkron, maksud Anda menunggu permintaan Ajax selesai?
Peter Aron Zentai
6
Catatan, while (not all are done) { }tidak akan berhasil. Saat Anda sibuk menunggu, tidak ada panggilan balik Anda yang dapat berjalan.
cHao
Iya. Saya menunggu panggilan async ke API eksternal untuk dikembalikan sehingga itu akan mengaktifkan metode callback. Ya cHao, saya menyadarinya, itulah sebabnya saya meminta bantuan di sini: D
codersarepeople
Anda dapat mencoba ini: github.com/caolan/async Kumpulan fungsi utilitas asinkron yang sangat bagus.
Paul Greyson

Jawaban:

191

Anda belum terlalu spesifik dengan kode Anda, jadi saya akan membuat skenario. Katakanlah Anda memiliki 10 panggilan ajax dan Anda ingin mengumpulkan hasil dari 10 panggilan ajax tersebut dan kemudian ketika semuanya telah selesai Anda ingin melakukan sesuatu. Anda dapat melakukannya seperti ini dengan mengumpulkan data dalam array dan melacak kapan yang terakhir selesai:

Penghitung Manual

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

Catatan: penanganan error penting di sini (tidak ditampilkan karena ini khusus untuk cara Anda melakukan panggilan ajax). Anda akan ingin memikirkan bagaimana Anda akan menangani kasus ketika satu panggilan ajax tidak pernah selesai, baik dengan kesalahan atau macet untuk waktu yang lama atau waktu habis setelah waktu yang lama.


jQuery Janji

Menambah jawaban saya pada tahun 2014. Akhir-akhir ini, $.ajax()promise sering digunakan untuk menyelesaikan masalah jenis ini karena jQuery sudah mengembalikan sebuah promise dan $.when()akan memberi tahu Anda saat sekelompok promise telah diselesaikan dan akan mengumpulkan hasil yang dikembalikan untuk Anda:

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

Janji Standar ES6

Seperti yang ditentukan dalam jawaban kba : jika Anda memiliki lingkungan dengan promise bawaan (browser modern atau node.js atau menggunakan babeljs transpile atau menggunakan promise polyfill), Anda dapat menggunakan promise yang ditentukan ES6. Lihat tabel ini untuk dukungan browser. Janji didukung di hampir semua browser saat ini, kecuali IE.

Jika doAjax()mengembalikan sebuah promise, Anda dapat melakukan ini:

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

Jika Anda perlu membuat operasi asinkron non-promise menjadi operasi yang mengembalikan promise, Anda bisa "menjanjikan" seperti ini:

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

Dan, kemudian gunakan pola di atas:

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

Janji Bluebird

Jika Anda menggunakan pustaka yang lebih kaya fitur seperti pustaka janji Bluebird , maka itu memiliki beberapa fungsi tambahan bawaan untuk membuatnya lebih mudah:

 var doAjax = Promise.promisify(someAsync);
 var someData = [...]
 Promise.map(someData, doAjax).then(function(results) {
     // all ajax results here
 }, function(err) {
     // some error here
 });
jfriend00
sumber
4
@ kba - Saya tidak akan menyebut jawaban ini ketinggalan jaman karena semua teknik masih berlaku, terutama jika Anda sudah menggunakan jQuery untuk Ajax. Tapi, saya telah memperbaruinya dengan beberapa cara untuk menyertakan janji asli.
jfriend00
Saat ini ada solusi yang jauh lebih bersih yang bahkan tidak membutuhkan jquery. Saya melakukannya dengan FetchAPI and Promises
philx_x
@philx_x - Apa yang Anda lakukan tentang dukungan IE dan Safari?
jfriend00
@ jfriend00 github membuat polyfill github.com/github/fetch . Atau saya tidak yakin apakah babel mendukung pengambilan. babeljs.io
philx_x
@philx_x - Kupikir begitu. Anda membutuhkan pustaka polyfill untuk menggunakan fetch saat ini. Tidak menghiraukan komentar Anda tentang menghindari perpustakaan ajax. Pengambilan memang bagus, tapi butuh waktu bertahun-tahun lagi untuk dapat menggunakannya tanpa polyfill. Ini bahkan belum ada dalam versi terbaru dari semua browser. Ya, itu tidak benar-benar mengubah apa pun dalam jawaban saya. Saya punya doAjax()janji yang mengembalikan sebagai salah satu opsi. Sama seperti fetch().
jfriend00
17

Memeriksa dari 2015: Kami sekarang memiliki janji asli di browser terbaru (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 dan browser Android 4.4.4 dan iOS Safari 8.4, tetapi tidak Internet Explorer, Opera Mini, dan versi yang lebih lama Android).

Jika kita ingin melakukan 10 tindakan asinkron dan mendapatkan pemberitahuan ketika semuanya sudah selesai, kita dapat menggunakan yang asli Promise.all, tanpa pustaka eksternal:

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);
});
kba
sumber
2
Promises.all()seharusnya Promise.all().
jfriend00
1
Jawaban Anda juga harus mengacu pada browser mana yang dapat Anda gunakan Promise.all()yang tidak menyertakan versi IE terkini.
jfriend00
10

Anda bisa menggunakan objek Deferred jQuery bersama dengan metode when .

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
});
Paul
sumber
7
Pertanyaan tidak diberi tag jQueryyang biasanya berarti OP tidak menginginkan jawaban jQuery.
jfriend00
8
@ jfriend00 Saya tidak ingin menemukan kembali roda ketika sudah dibuat di jQuery
Paul
4
@Paul jadi daripada menemukan kembali roda Anda termasuk sampah
40kb
2
Tetapi tidak semua orang dapat atau ingin menggunakan jQuery dan kebiasaan di SO adalah Anda menunjukkannya dengan apakah Anda menandai pertanyaan Anda dengan jQuery atau tidak.
jfriend00
4
$ .When panggilan contoh ini salah. Untuk menunggu array deferred / promise Anda perlu menggunakan $ .when.apply ($, promise) .then (function () {/ * do stuff * /}).
danw
9

Anda dapat menirunya seperti ini:

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

maka setiap panggilan asinkron melakukan ini:

countDownLatch.count++;

sementara di setiap panggilan asinkron kembali di akhir metode Anda menambahkan baris ini:

countDownLatch.check();

Dengan kata lain, Anda meniru fungsi hitung-mundur-latch.

Eugene Retunsky
sumber
Dalam 99% dari semua kasus penggunaan, Promise adalah cara yang harus ditempuh tetapi saya suka jawaban ini karena ini menggambarkan metode untuk mengelola kode Async dalam situasi di mana polyfill Promise lebih besar daripada JS yang menggunakannya!
Sukima
6

Ini cara yang paling rapi menurut saya.

Promise.all

FetchAPI

(untuk beberapa alasan Array.map tidak berfungsi di dalam. lalu fungsi untuk saya. Tetapi Anda dapat menggunakan .forEach dan [] .concat () atau yang serupa)

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);
})
philx_x
sumber
1
Saya pikir ini perlu return responses.map(response => { return response.json(); }), atau return responses.map(response => response.json()).
1

Gunakan pustaka aliran kontrol seperti 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)
})
Raynos
sumber