Apa yang dilakukan $ .when.apply ($, someArray)?

110

Saya membaca tentang Deferreds and Promises dan terus membaca $.when.apply($, someArray). Saya sedikit tidak jelas tentang apa yang sebenarnya dilakukannya, mencari penjelasan bahwa satu baris berfungsi dengan tepat (bukan keseluruhan cuplikan kode). Berikut beberapa konteksnya:

var data = [1,2,3,4]; // the ids coming back from serviceA
var processItemsDeferred = [];

for(var i = 0; i < data.length; i++){
  processItemsDeferred.push(processItem(data[i]));
}

$.when.apply($, processItemsDeferred).then(everythingDone); 

function processItem(data) {
  var dfd = $.Deferred();
  console.log('called processItem');

  //in the real world, this would probably make an AJAX call.
  setTimeout(function() { dfd.resolve() }, 2000);    

  return dfd.promise();
}

function everythingDone(){
  console.log('processed all items');
}
manafire
sumber
1
.done()dapat digunakan sebagai pengganti .thendalam kasus ini, cukup FYI
Kevin B
2
fwiw, ada port Deferred untuk menggarisbawahi yang memungkinkan melewatkan satu array _.whensehingga Anda tidak perlu menggunakanapply
Eevee
Pelajari lebih lanjut tentang .apply: developer.mozilla.org/en-US/docs/JavaScript/Reference/… .
Felix Kling
1
Artikel yang dirujuk OP di kalimat pertamanya telah pindah lokasi - sekarang ada di: flaviocopes.com/blog/deferreds-and-promises-in-javascript .
glaucon

Jawaban:

161

.applydigunakan untuk memanggil fungsi dengan larik argumen. Ini mengambil setiap elemen dalam larik, dan menggunakan masing-masing sebagai parameter untuk fungsi. .applyjuga bisa mengubah konteks ( this) di dalam fungsi.

Jadi, ayo ambil $.when. Ini digunakan untuk mengatakan "ketika semua janji ini diselesaikan ... lakukan sesuatu". Dibutuhkan jumlah parameter yang tak terbatas (variabel).

Dalam kasus Anda, Anda memiliki serangkaian janji; Anda tidak tahu berapa banyak parameter yang Anda teruskan $.when. Meneruskan array itu sendiri $.whentidak akan berfungsi, karena ia mengharapkan parameternya berupa promise, bukan array.

Di situlah .applymasuk. Dibutuhkan array, dan memanggil $.whendengan setiap elemen sebagai parameter (dan memastikan thisdisetel ke jQuery/ $), jadi semuanya berfungsi :-)

Roket Hazmat
sumber
3
ketika beberapa janji diteruskan ke metode $ .when. Dalam urutan apa mereka akan mengeksekusi? satu demi satu atau secara paralel?
Darshan
21
@Darshan: Anda tidak "menjalankan" janji. Anda menunggu sampai mereka diselesaikan. Mereka dieksekusi saat dibuat, $.whentunggu saja semuanya selesai sebelum melanjutkan.
Rocket Hazmat
1
bagaimana dengan perbedaan antara $.when($, arrayOfPromises).done(...) dan $.when(null, arrayOfPromises).done(...) (yang saya temukan keduanya sebagai solusi yang diusulkan di forum ...)
zeroquaranta
63

$ .when mengambil sejumlah parameter dan menyelesaikan saat semua ini telah diselesaikan.

anyFunction .apply (thisValue, arrayParameters) memanggil fungsi anyFunction yang menyetel konteksnya (thisValue akan menjadi ini dalam pemanggilan fungsi itu) dan meneruskan semua objek dalam arrayParameters sebagai parameter individual.

Sebagai contoh:

$.when.apply($, [def1, def2])

Sama dengan:

$.when(def1, def2)

Tapi menerapkan cara panggilan memungkinkan Anda untuk melewatkan array jumlah yang tidak diketahui parameter. (Dalam kode Anda, Anda mengatakan bahwa data Anda berasal dari layanan, maka itulah satu-satunya cara untuk memanggil $ .when )

Pablo
sumber
15

Di sini, kode didokumentasikan dengan lengkap.

// 1. Declare an array of 4 elements
var data = [1,2,3,4]; // the ids coming back from serviceA
// 2. Declare an array of Deferred objects
var processItemsDeferred = [];

// 3. For each element of data, create a Deferred push push it to the array
for(var i = 0; i < data.length; i++){
  processItemsDeferred.push(processItem(data[i]));
}

// 4. WHEN ALL Deferred objects in the array are resolved THEN call the function
//    Note : same as $.when(processItemsDeferred[0], processItemsDeferred[1], ...).then(everythingDone);
$.when.apply($, processItemsDeferred).then(everythingDone); 

// 3.1. Function called by the loop to create a Deferred object (data is numeric)
function processItem(data) {
  // 3.1.1. Create the Deferred object and output some debug
  var dfd = $.Deferred();
  console.log('called processItem');

  // 3.1.2. After some timeout, resolve the current Deferred
  //in the real world, this would probably make an AJAX call.
  setTimeout(function() { dfd.resolve() }, 2000);    

  // 3.1.3. Return that Deferred (to be inserted into the array)
  return dfd.promise();
}

// 4.1. Function called when all deferred are resolved
function everythingDone(){
  // 4.1.1. Do some debug trace
  console.log('processed all items');
}
Yanick Rochon
sumber
7
$.when.apply($, array)adalah tidak sama dengan $.when(array). Ini sama dengan:$.when(array[0], array[1], ...)
Rocket Hazmat
1
Itulah alasan utama mengapa digunakan dengan .apply , Anda tidak tahu banyak elemen processItemsDeferred miliki
Pablo
2

Sayangnya saya tidak setuju dengan kalian.

$.when.apply($, processItemsDeferred).always(everythingDone);

Akan menelepon everythingDonesegera setelah satu penangguhan ditolak , meskipun ada penangguhan lain yang menunggu keputusan .

Berikut skrip lengkapnya (saya sarankan http://jsfiddle.net/ ):

var data = [1,2,3,4]; // the ids coming back from serviceA
var processItemsDeferred = [];

for(var i = 0; i < data.length; i++){
  processItemsDeferred.push(processItem(data[i]));
}

processItemsDeferred.push($.Deferred().reject());
//processItemsDeferred.push($.Deferred().resolve());

$.when.apply($, processItemsDeferred).always(everythingDone); 

function processItem(data) {
  var dfd = $.Deferred();
  console.log('called processItem');

  //in the real world, this would probably make an AJAX call.
  setTimeout(function() { dfd.resolve(); }, 2000);    

  return dfd.promise();
}

function everythingDone(){
  alert('processed all items');
}

Apakah ini bug? Saya ingin menggunakan ini seperti yang dijelaskan pria di atas.

pengguna3388213
sumber
1
Penolakan pertama akan mengaktifkan selalu, tetapi tidak. Kemudian. Lihat jsfiddle.net/logankd/s5dacgb3 saya yang saya buat dari contoh Anda. Saya menggunakan JQuery 2.1.0 dalam contoh ini.
Diselaraskan
1
Ini seperti yang dimaksudkan. Ada banyak kasus di mana Anda ingin tahu segera setelah ada yang gagal, tidak menunggu semuanya selesai dan memeriksa apakah ada kegagalan. Terutama jika pemrosesan tidak dapat dilanjutkan setelah ada kegagalan, mengapa menunggu sisanya selesai / gagal? Seperti yang disarankan oleh komentar lain, Anda dapat menggunakan pasangan .then atau .fail & .done.
MPavlak
@GoneCoding Ini tidak berguna. OP menanyakan apa yang apply () lakukan dan Anda menyarankan alternatif buruk yang tidak boleh digunakan :) untuk itulah tombol suara turun. Saya juga tidak menggunakannya sampai Anda menolak untuk memberikan MENGAPA Anda melakukannya mengapa (lebih dari preferensi Anda untuk menghindari array karena suatu alasan)
MPavlak
@GoneCoding Terima kasih telah menghapus jawaban itu
MPavlak
1
@GoneCoding lol, saya membaca solusi Anda dan memberikan umpan balik. Anda tidak memberikan jawaban untuk pertanyaan awal. Anda tidak bisa menjelaskan mengapa itu seperti itu. Orang-orang seperti Anda yang memberikan solusi buruk kepada orang-orang yang sedang belajar. Anda jelas memiliki keterampilan javascript yang terbatas dan membawa saya menjadi n00b. Saya menunjukkan mengapa itu salah dan Anda bahkan tidak bisa membaca kodenya dan malah memberi tahu saya bahwa saya salah. kerja bagus sobat!
MPavlak
1

Mungkin seseorang dapat menemukan ini berguna:

$.when.apply($, processItemsDeferred).then(everythingDone).fail(noGood);

everythingDone tidak dipanggil jika ada penolakan

Vlado Kurelec
sumber
0

Terima kasih atas solusi elegan Anda:

var promise;

for(var i = 0; i < data.length; i++){
  promise = $.when(promise, processItem(data[i]));
}

promise.then(everythingDone);

Hanya satu poin: Saat menggunakan resolveWithuntuk mendapatkan beberapa parameter, itu rusak karena janji awal disetel ke tidak ditentukan. Apa yang saya lakukan untuk membuatnya berhasil:

// Start with an empty resolved promise - undefined does the same thing!
var promise;

for(var i = 0; i < data.length; i++){
  if(i==0) promise = processItem(data[i]);
  else promise = $.when(promise, processItem(data[i]));
}

promise.then(everythingDone);
pengguna3544352
sumber
2
Sementara itu berhasil, itu tidak benar-benar elegan. Anda membuat janji yang merepresentasikan penundaan up-to-ith yang dilakukan sehingga iterasi terakhir berisi "when (workToDo [0..i-1], workToDo [i])" atau lebih jelasnya "saat semua pekerjaan sebelumnya dan ini pekerjaan selesai ". Ini berarti Anda memiliki i + 1 saat membungkus janji Anda. Juga, ketika melakukan hal-hal semacam ini, cukup buka iterasi pertama. var promise = processItem (data [0]); untuk (var i = 1; i <data.length; i ++) {promise = $ .when (promise, processItem (data [i])); }
MPavlak