ผ่านอาร์เรย์ของ Deferreds เป็น $. เมื่อ ()


447

นี่คือตัวอย่างที่คาดการณ์ไว้ของสิ่งที่เกิดขึ้น: http://jsfiddle.net/adamjford/YNGcm/20/

HTML:

<a href="#">Click me!</a>
<div></div>

JavaScript:

function getSomeDeferredStuff() {
    var deferreds = [];

    var i = 1;
    for (i = 1; i <= 10; i++) {
        var count = i;

        deferreds.push(
        $.post('/echo/html/', {
            html: "<p>Task #" + count + " complete.",
            delay: count
        }).success(function(data) {
            $("div").append(data);
        }));
    }

    return deferreds;
}

$(function() {
    $("a").click(function() {
        var deferreds = getSomeDeferredStuff();

        $.when(deferreds).done(function() {
            $("div").append("<p>All done!</p>");
        });
    });
});

ฉันต้องการ "เสร็จแล้ว!" เพื่อปรากฏหลังจากงานรอการตัดบัญชีทั้งหมดเสร็จสมบูรณ์แล้ว แต่$.when()ดูเหมือนจะไม่ทราบวิธีจัดการอาร์เรย์ของวัตถุรอการตัดบัญชี "ทุกอย่างเสร็จเรียบร้อย!" เกิดขึ้นก่อนเพราะอาร์เรย์ไม่ใช่วัตถุที่เลื่อนออกไปดังนั้น jQuery จึงดำเนินต่อไปและสมมติว่ามันเพิ่งเสร็จสิ้น

ฉันรู้ว่าสามารถส่งผ่านวัตถุไปยังฟังก์ชันได้$.when(deferred1, deferred2, ..., deferredX)แต่ไม่ทราบว่ามีวัตถุรอการตัดบัญชีจำนวนเท่าไรที่จะถูกดำเนินการในปัญหาจริงที่ฉันพยายามแก้ไข



เพิ่มคำถามใหม่ที่เรียบง่ายกว่าเดิมสำหรับคำถามเก่า ๆ ด้านล่างนี้ คุณไม่จำเป็นต้องใช้อาเรย์หรือ$.when.applyเลยเพื่อให้ได้ผลลัพธ์เดียวกัน
Gone Coding

ย้อนกลับหัวข้อคำถามเนื่องจากเฉพาะเจาะจงเกินไป (นี่ไม่ใช่แค่ปัญหา AJAX)
Alnitak

คำตอบ:


732

หากต้องการส่งผ่านอาร์เรย์ของค่าไปยังฟังก์ชันใด ๆที่โดยปกติคาดว่าจะเป็นพารามิเตอร์ที่แยกต่างหากให้ใช้Function.prototype.applyดังนั้นในกรณีนี้คุณต้อง:

$.when.apply($, my_array).then( ___ );

ดูhttp://jsfiddle.net/YNGcm/21/

ใน ES6 คุณสามารถใช้ตัว... ดำเนินการสเปรดแทน:

$.when(...my_array).then( ___ );

ไม่ว่าในกรณีใดเนื่องจากเป็นไปได้ยากที่คุณจะทราบล่วงหน้าว่า.thenจะต้องใช้พารามิเตอร์ตัวใดตัวจัดการจำนวนมากตัวจัดการนั้นจะต้องประมวลผลargumentsอาร์เรย์เพื่อรับผลลัพธ์ของสัญญาแต่ละสัญญา


4
มันใช้งานได้ดีมาก :) ฉันประหลาดใจที่ฉันไม่สามารถขุดขึ้นมาได้ง่ายๆผ่าน Google!
adamjford

9
นั่นเป็นเพราะมันเป็นวิธีการทั่วไปไม่เฉพาะ$.when- f.apply(ctx, my_array)จะเรียกfด้วยthis == ctxและข้อโต้แย้งที่กำหนดเป็นเนื้อหาmy_arrayของ
Alnitak

4
@Alnitak: ฉันอายเล็กน้อยที่ฉันไม่รู้เกี่ยวกับวิธีการนี้โดยพิจารณาว่าฉันเขียน JavaScript นานแค่ไหน!
adamjford

5
FWIW ลิงก์ในคำตอบของ Eli สำหรับคำถาม earler พร้อมการอภิปรายเกี่ยวกับการส่งผ่าน$และnullเนื่องจากพารามิเตอร์ตัวแรกมีค่าต่อการอ่าน ในกรณีพิเศษนี้มันไม่สำคัญว่า
Alnitak

4
@Alnitak: ใช่ แต่$พิมพ์น้อยกว่าnullและคุณปลอดภัยเมื่อมี$.whenการเปลี่ยนแปลงการใช้งาน (ไม่ใช่ว่าเป็นไปได้ในกรณีนี้ แต่ทำไมไม่thisเปลี่ยนแปลงตามค่าเริ่มต้น)
Tomasz Zieliński

109

การแก้ปัญหาด้านบน (ขอบคุณ!) ไม่ได้แก้ไขปัญหาอย่างถูกต้องในการเรียกคืนออบเจ็กต์ที่ให้ไว้กับresolve()วิธีการรอการตัดบัญชีเนื่องจาก jQuery เรียกdone()และการfail()เรียกกลับด้วยพารามิเตอร์แต่ละตัวไม่ใช่อาร์เรย์ นั่นหมายความว่าเราต้องใช้argumentspseudo-array เพื่อรับวัตถุที่ถูกแก้ไข / ปฏิเสธทั้งหมดที่ส่งคืนโดย array ของ deferreds ซึ่งน่าเกลียด:

$.when.apply($,deferreds).then(function() {
     var objects=arguments; // The array of resolved objects as a pseudo-array
     ...
};

เนื่องจากเราผ่านไปในช่วงรอการตัดบัญชีมันจะเป็นการดีหากได้รับผลลัพธ์กลับมามากมาย Array.sort()นอกจากนี้ยังจะดีที่จะได้รับกลับอาร์เรย์ที่เกิดขึ้นจริงแทนการหลอกอาร์เรย์เพื่อให้เราสามารถใช้วิธีการเช่น

นี่คือวิธีการแก้ปัญหาแรงบันดาลใจจากwhen.js 's when.all()วิธีการที่อยู่ปัญหาเหล่านี้:

// Put somewhere in your scripting environment
if (typeof jQuery.when.all === 'undefined') {
    jQuery.when.all = function (deferreds) {
        return $.Deferred(function (def) {
            $.when.apply(jQuery, deferreds).then(
                function () {
                    def.resolveWith(this, [Array.prototype.slice.call(arguments)]);
                },
                function () {
                    def.rejectWith(this, [Array.prototype.slice.call(arguments)]);
                });
        });
    }
}

ตอนนี้คุณสามารถผ่านอาร์เรย์ของ deferreds / contract และรับอาร์เรย์ของวัตถุที่ถูกแก้ไข / ปฏิเสธในการเรียกกลับของคุณเช่น:

$.when.all(deferreds).then(function(objects) {
    console.log("Resolved objects:", objects);
});

6
คุณอาจต้องการใช้ resolWith และ rejectWith เพื่อให้คุณได้รับ deferreds เดิมเช่นเดียวกับ 'this' deferred.resolveWith (นี่ [Array.prototype.slice.call (อาร์กิวเมนต์)) ฯลฯ
Jamie Pate

1
มีปัญหาเล็ก ๆ เกี่ยวกับรหัสของคุณเมื่อมีองค์ประกอบเดียวเท่านั้นในอาร์เรย์อาร์เรย์ผลลัพธ์จะส่งกลับผลลัพธ์นั้นแทนที่จะเป็นอาร์เรย์ที่มีองค์ประกอบเดียว (ซึ่งจะทำลายรหัสที่คาดว่าอาร์เรย์) การแก้ไขปัญหานั้นใช้ฟังก์ชันนี้แทนvar toArray = function (args) { return deferreds.length > 1 ? $.makeArray(args) : [args]; } Array.prototype.slice.call
Luan Nico

หืมนี่ดูเหมือนจะไม่จับ 404 ของใด ๆ
t.mikael.d

พบเหตุผล.. ไฟล์ควรเป็น. ปฏิเสธแทน - เพื่อให้สามารถจับ 404 ได้
t.mikael.d

38

คุณสามารถใช้whenวิธีนี้กับอาเรย์ของคุณ:

var arr = [ /* Deferred objects */ ];

$.when.apply($, arr);

คุณทำงานกับอาร์เรย์ของ jQuery Deferreds อย่างไร


ฉันเห็นคำถามนั้นจริง ๆ แต่ฉันเดาว่ารายละเอียดเพิ่มเติมทั้งหมดในคำถามนั้นทำให้คำตอบสำหรับปัญหาของฉัน (ซึ่งอยู่ตรงนั้น) บินตรงไปที่หัวของฉัน
adamjford

1
@adamjford หากมันทำให้คุณรู้สึกดีขึ้นฉันพบว่าคำถามของคุณง่ายต่อการบริโภค (และก่อนอื่นในการค้นหาโดยเฉพาะ Google ของฉันสำหรับปัญหาที่แน่นอนนี้)
patridge

@patridge: ดีใจที่ได้ยินมันช่วยคุณได้!
adamjford

นี่เป็นคำตอบที่ดี แต่มันก็ไม่ชัดเจนสำหรับฉันที่จะนำไปใช้กับตัวอย่างในคำถามเดิม หลังจากปรึกษาคำถามที่เชื่อมโยงแล้วจะเห็นได้ชัดว่าบรรทัด "$ .when (deferreds) .done (function () {" ควรเปลี่ยนเป็น "$ .when.apply ($, deferreds) .done (function () { ใช่มั้ย
สมเด็จพระสันตะปาปาการ์แลนด์

7

เมื่อทำการโทร AJAX ขนานหลายสายคุณมีสองตัวเลือกในการจัดการการตอบกลับที่เกี่ยวข้อง

  1. ใช้การโทร AJAX แบบซิงโครนัส / หลังจากนั้นไม่แนะนำ / ไม่แนะนำ
  2. ใช้Promises'อาร์เรย์และ$.whenที่ยอมรับpromises และ callback .doneถูกเรียกเมื่อpromises ทั้งหมดกลับมาเรียบร้อยแล้วพร้อมการตอบกลับที่เกี่ยวข้อง

ตัวอย่าง

function ajaxRequest(capitalCity) {
   return $.ajax({
        url: 'https://restcountries.eu/rest/v1/capital/'+capitalCity,
        success: function(response) {
        },
        error: function(response) {
          console.log("Error")
        }
    });
}
$(function(){
   var capitalCities = ['Delhi', 'Beijing', 'Washington', 'Tokyo', 'London'];
   $('#capitals').text(capitalCities);

   function getCountryCapitals(){ //do multiple parallel ajax requests
      var promises = [];   
      for(var i=0,l=capitalCities.length; i<l; i++){
            var promise = ajaxRequest(capitalCities[i]);
            promises.push(promise);
      }
  
      $.when.apply($, promises)
        .done(fillCountryCapitals);
   }
  
   function fillCountryCapitals(){
        var countries = [];
        var responses = arguments;
        for(i in responses){
            console.dir(responses[i]);
            countries.push(responses[i][0][0].nativeName)
        }  
        $('#countries').text(countries);
   }
  
   getCountryCapitals()
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
  <h4>Capital Cities : </h4> <span id="capitals"></span>
  <h4>Respective Country's Native Names : </h4> <span id="countries"></span>
</div>


1
คำตอบของคุณมีมากเกินไปและคุณแก้ไขชื่อคำถามด้วยเช่นกัน OP ทราบวิธีการโทร AJAX และรับวัตถุที่เลื่อนออกไปจำนวนมากแล้ว แต่เพียงผู้เดียว$.whenจุดคำถามคือวิธีการผ่านอาร์เรย์ที่
Alnitak

5
ฉันคิดว่าการอธิบายรายละเอียดพร้อมตัวอย่างจะดีกว่าด้วยตัวเลือกที่มีอยู่และสำหรับฉันไม่คิดว่าการลงคะแนนนั้นเป็นสิ่งที่จำเป็น
vinayakj

2
downvote สำหรับ 1. แม้จะแนะนำการซิงค์ (ถึงแม้ว่าจะไม่ได้แนะนำก็ตาม) 2. โค้ดที่มีคุณภาพต่ำในตัวอย่าง (รวมถึงfor ... inอาเรย์ด้วย!)
Alnitak

1
1. ตกลงเห็นด้วยควรมี (not recommended)2. ไม่เห็นด้วย - for ... inไม่เป็นไรเพราะอาร์เรย์มีเฉพาะคุณสมบัติที่ต้องการ (ไม่มีคุณสมบัติพิเศษ) thanx anyways
vinayakj

1
Re: 2 - ปัญหาคือมันอาจจะได้รับการคัดลอกโดยคนอื่น ๆ Array.prototypeที่ไม่สามารถทำให้การรับประกันว่าหรือได้รับเพียงพอใบ้ที่จะเพิ่ม ไม่ว่าในกรณีใด ๆ สำหรับโค้ดที่ไม่เกี่ยวกับประสิทธิภาพมันจะดีกว่าที่จะใช้.mapแทน a for/ pushloop เช่นvar promises = capitalCities.map(ajaxRequest); $.when.apply($, promises).then(fillCountryCapitals)- งานเสร็จสิ้น
Alnitak

6

ในฐานะทางเลือกง่าย ๆ ที่ไม่ต้องการ$.when.applyหรือarrayคุณสามารถใช้รูปแบบต่อไปนี้เพื่อสร้างสัญญาเดียวสำหรับสัญญาหลายขนาน:

promise = $.when(promise, anotherPromise);

เช่น

function GetSomeDeferredStuff() {
    // Start with an empty resolved promise (or undefined does the same!)
    var promise;
    var i = 1;
    for (i = 1; i <= 5; i++) {
        var count = i;

        promise = $.when(promise,
        $.ajax({
            type: "POST",
            url: '/echo/html/',
            data: {
                html: "<p>Task #" + count + " complete.",
                delay: count / 2
            },
            success: function (data) {
                $("div").append(data);
            }
        }));
    }
    return promise;
}

$(function () {
    $("a").click(function () {
        var promise = GetSomeDeferredStuff();
        promise.then(function () {
            $("div").append("<p>All done!</p>");
        });
    });
});

หมายเหตุ:

  • ฉันคิดว่าอันนี้ออกมาหลังจากที่เห็นใครบางคนในเครือสัญญาตามลำดับโดยใช้ promise = promise.then(newpromise)
  • ข้อเสียคือมันสร้างวัตถุสัญญาพิเศษเบื้องหลังและพารามิเตอร์ใด ๆ ที่ผ่านในตอนท้ายไม่มีประโยชน์มาก (เนื่องจากซ้อนอยู่ในวัตถุเพิ่มเติม) สำหรับสิ่งที่คุณต้องการแม้ว่ามันจะสั้นและเรียบง่าย
  • ข้อดีคือไม่ต้องมีการจัดการอาร์เรย์หรืออาร์เรย์

2
แก้ไขให้ฉันถ้าฉันผิด แต่วิธีการของคุณซ้อนอยู่อย่างมีประสิทธิภาพ $. เมื่อ ($. เมื่อ ($. เมื่อไหร่ (... ))) ดังนั้นคุณจึงทำซ้ำซ้อนกัน 10 ระดับซ้ำถ้ามี 10 ซ้ำ ดูเหมือนจะไม่ขนานกันมากนักเนื่องจากคุณต้องรอให้แต่ละระดับส่งคืนสัญญาที่ซ้อนอยู่ของเด็ก ๆ ก่อนที่จะสามารถคืนสัญญาของตัวเองได้ - ฉันคิดว่าวิธีการเรียงลำดับในคำตอบที่ยอมรับนั้นสะอาดกว่ามากเพราะมันใช้พฤติกรรมพารามิเตอร์ที่ยืดหยุ่น วิธี $. เมื่อ ()
Anthony McLin

@AnthonyMcLin: นี่คือจุดประสงค์เพื่อให้ทางเลือกที่ง่ายกว่าในการเขียนโค้ดไม่ใช่ประสิทธิภาพที่ดีกว่า (ซึ่งเล็กน้อยกับการเข้ารหัส Async ส่วนใหญ่) เช่นเดียวกับการผูกthen()สายในลักษณะที่คล้ายกัน พฤติกรรมที่มี$.whenคือการทำหน้าที่เป็นมันขนาน (ไม่ถูกล่ามโซ่) โปรดลองมันก่อนที่จะโยนออกไปเป็นทางเลือกที่มีประโยชน์เป็นมันไม่ทำงาน :)
Gone Coding

2
@Alnitak: ม้าสำหรับหลักสูตร คุณมีสิทธิ์แสดงความเห็น แต่คุณไม่ได้ใช้สิ่งนี้ด้วยตนเอง ความคิดเห็นของฉันขึ้นอยู่กับการใช้งานจริงของเทคนิคนี้ มันใช้งานได้และมีประโยชน์ทำไมจึงโยนเครื่องมือออกจากกล่องเครื่องมือโดยอ้างอิงจากการพูดเกินจริงเช่น "โหลดของ caveats" (หนึ่ง) และ "แก้ปัญหาอะไร" (ไม่จริง - มันกำจัดการประมวลผลอาร์เรย์และลดความซับซ้อนของสัญญาแบบขนาน ค่าไม่จำเป็นต้องใช้ซึ่งอย่างที่คุณควรรู้นั้นไม่ค่อยได้ใช้ในกรณีประมวลผลแบบขนาน) Downvotes ควรเป็น "คำตอบนี้ไม่มีประโยชน์" :)
Gone Coding

1
สวัสดี @GoneCoding ฉันขอให้คุณไม่เพิ่มความเห็นให้กับคำตอบของคุณได้ไหม? เหมาะสำหรับการแสดงความคิดเห็น แต่อย่างอื่นมันเป็นเสียงที่เบี่ยงเบนความสนใจจากเนื้อหาที่ดี ขอบคุณ
halfer

1
@ halfer: ฉันไม่ได้โพสต์ใด ๆ อีก แต่ฉันรำคาญที่ไม่รู้ที่แสดงให้เห็นถึงสิ่งที่เป็นต้นฉบับ รักษาความคิดใหม่ ๆ ไว้กับตัวเองในปัจจุบัน :)
Gone Coding

4

ฉันต้องการเสนออีกอันด้วยการใช้ $ .each:

  1. เราอาจจะประกาศฟังก์ชัน ajax เช่น:

    function ajaxFn(someData) {
        this.someData = someData;
        var that = this;
        return function () {
            var promise = $.Deferred();
            $.ajax({
                method: "POST",
                url: "url",
                data: that.someData,
                success: function(data) {
                    promise.resolve(data);
                },
                error: function(data) {
                    promise.reject(data);
                }
            })
            return promise;
        }
    }
  2. ส่วนหนึ่งของรหัสที่เราสร้างฟังก์ชั่นมากมายด้วย ajax เพื่อส่ง:

    var arrayOfFn = [];
    for (var i = 0; i < someDataArray.length; i++) {
        var ajaxFnForArray = new ajaxFn(someDataArray[i]);
        arrayOfFn.push(ajaxFnForArray);
    }
  3. และฟังก์ชั่นการโทรด้วยการส่ง ajax:

    $.when(
        $.each(arrayOfFn, function(index, value) {
            value.call()
        })
    ).then(function() {
            alert("Cheer!");
        }
    )

1

หากคุณ transpiling และมีสิทธิ์เข้าถึง ES6 คุณสามารถใช้ไวยากรณ์การแพร่กระจายซึ่งใช้เฉพาะรายการที่สามารถทำซ้ำได้ของวัตถุเป็นอาร์กิวเมนต์แบบไม่ต่อเนื่องโดยเฉพาะตามที่$.when()ต้องการ

$.when(...deferreds).done(() => {
    // do stuff
});

ลิงค์ MDN - ไวยากรณ์สเปรด


0

หากคุณกำลังใช้ angularJS หรือบางส่วนของไลบรารี่สัญญา Q คุณก็มี.all()วิธีที่จะแก้ปัญหานี้ได้แน่นอน

var savePromises = [];
angular.forEach(models, function(model){
  savePromises.push(
    model.saveToServer()
  )
});

$q.all(savePromises).then(
  function success(results){...},
  function failed(results){...}
);

ดู API แบบเต็ม:

https://github.com/kriskowal/q/wiki/API-Reference#promiseall

https://docs.angularjs.org/api/ng/service/$q


4
นี่ไม่เกี่ยวข้องอย่างสมบูรณ์
Benjamin Gruenbaum

@BenjaminGruenbaum เป็นไงบ้าง? javascript สัญญาทั้งหมดแชร์ API ที่คล้ายกันและไม่มีอะไรผิดปกติในการแสดงการใช้งานที่แตกต่างกัน ฉันมาถึงหน้านี้เพื่อค้นหาคำตอบสำหรับเชิงมุมและฉันสงสัยว่าผู้ใช้รายอื่นจำนวนมากจะมาถึงหน้านี้และไม่จำเป็นต้องอยู่ในสภาพแวดล้อมแบบ jquery เท่านั้น
mastaBlasta

2
กล่าวคือเนื่องจากคำสัญญาของ jQuery ไม่ได้เปิดเผยAPI นี้จึงไม่เหมาะสมอย่างสมบูรณ์ในฐานะคำตอบของ Stack Overflow - มีคำตอบที่คล้ายกันสำหรับ Angular และคุณสามารถถามได้ที่นั่น (ไม่ต้องพูดถึงคุณควรมา.mapที่นี่ แต่ก็ดีนะ)
Benjamin Gruenbaum

0

ฉันมีกรณีคล้ายกันมากที่ฉันโพสต์ในแต่ละวงแล้วตั้งค่ามาร์กอัพ html ในบางฟิลด์จากตัวเลขที่ได้รับจาก ajax จากนั้นฉันต้องทำผลรวมของค่า (อัปเดตตอนนี้) ของฟิลด์เหล่านี้และวางในฟิลด์ทั้งหมด

ดังนั้นปัญหาคือว่าฉันพยายามที่จะทำผลรวมกับตัวเลขทั้งหมด แต่ไม่มีข้อมูลกลับมาจากการโทร async ajax ฉันต้องใช้ฟังก์ชันนี้ให้เสร็จเพื่อให้สามารถใช้รหัสซ้ำได้ ฟังก์ชั่นด้านนอกของฉันรอข้อมูลก่อนที่ฉันจะไปแล้วทำบางสิ่งกับ DOM ที่อัปเดตอย่างสมบูรณ์

    // 1st
    function Outer() {
        var deferreds = GetAllData();

        $.when.apply($, deferreds).done(function () {
            // now you can do whatever you want with the updated page
        });
    }

    // 2nd
    function GetAllData() {
        var deferreds = [];
        $('.calculatedField').each(function (data) {
            deferreds.push(GetIndividualData($(this)));
        });
        return deferreds;
    }

    // 3rd
    function GetIndividualData(item) {
        var def = new $.Deferred();
        $.post('@Url.Action("GetData")', function (data) {
            item.html(data.valueFromAjax);
            def.resolve(data);
        });
        return def;
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.