Chuyển trong một mảng của Trì hoãn thành $ .when ()


447

Đây là một ví dụ giả định về những gì đang diễn ra: 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>");
        });
    });
});

Tôi muốn "Tất cả đã hoàn tất!" xuất hiện sau khi tất cả các nhiệm vụ hoãn lại đã hoàn thành, nhưng $.when()dường như không biết cách xử lý một mảng các đối tượng Trì hoãn. "Tất cả đã được làm xong!" đang xảy ra đầu tiên vì mảng không phải là đối tượng Trì hoãn, vì vậy jQuery đi trước và cho rằng nó vừa hoàn thành.

Tôi biết người ta có thể chuyển các đối tượng vào hàm như thế nào $.when(deferred1, deferred2, ..., deferredX)nhưng không biết có bao nhiêu đối tượng Trì hoãn sẽ có lúc thực thi trong vấn đề thực tế mà tôi đang cố gắng giải quyết.



Đã thêm một câu trả lời mới, đơn giản hơn cho câu hỏi rất cũ này bên dưới. Bạn không cần phải sử dụng một mảng hoặc hoàn $.when.applytoàn để có được kết quả tương tự.
Mã hóa

trả lại chủ đề câu hỏi, vì nó quá cụ thể (đây không chỉ là vấn đề AJAX)
Alnitak

Câu trả lời:


732

Để truyền một mảng các giá trị cho bất kỳ hàm nào thường mong đợi chúng là các tham số riêng biệt, hãy sử dụng Function.prototype.apply, vì vậy trong trường hợp này bạn cần:

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

Xem http://jsfiddle.net/YNGcm/21/

Trong ES6, bạn có thể sử dụng ... toán tử trải rộng thay thế:

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

Trong cả hai trường hợp, vì không chắc là bạn sẽ biết trước bao nhiêu tham số chính thức mà .thentrình xử lý sẽ yêu cầu, trình xử lý đó sẽ cần xử lý argumentsmảng để lấy kết quả của mỗi lời hứa.


4
Điều này làm việc, tuyệt vời. :) Tôi rất ngạc nhiên vì tôi đã không thể thực hiện một thay đổi đơn giản như vậy thông qua Google!
adamjford

9
đó là bởi vì đó là một phương pháp chung chung, không cụ thể cho $.when- f.apply(ctx, my_array)sẽ gọi fvới this == ctxvà các đối số thiết lập để các nội dung của my_array.
Alnitak

4
@Alnitak: Tôi hơi xấu hổ vì tôi không biết về phương pháp đó, xem xét tôi đã viết JavaScript được bao lâu rồi!
adamjford

5
FWIW, liên kết trong câu trả lời của Eli cho câu hỏi của người nghe với thảo luận về việc vượt qua $so với nulltham số đầu tiên đáng để đọc. Trong trường hợp cụ thể này, nó không thành vấn đề.
Alnitak

4
@Alnitak: Có, nhưng $ít gõ hơn nullvà bạn an toàn khi $.whenthực hiện thay đổi (không có khả năng trong trường hợp này nhưng tại sao không thisthay đổi theo mặc định).
Tomasz Zieliński

109

Các cách giải quyết ở trên (cảm ơn!) Không giải quyết đúng vấn đề lấy lại các đối tượng được cung cấp cho người bị trì hoãn resolve() phương thức vì jQuery gọi done()fail()gọi lại với các tham số riêng lẻ, không phải là một mảng. Điều đó có nghĩa là chúng ta phải sử dụng argumentsmảng giả để có được tất cả các đối tượng được giải quyết / bị từ chối được trả về bởi mảng trả chậm, điều này thật xấu xí:

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

Vì chúng tôi đã thông qua một loạt các hoãn lại, sẽ rất tốt để lấy lại một loạt các kết quả. Nó cũng sẽ là tốt đẹp để lấy lại một mảng thực tế thay vì một mảng giả để chúng ta có thể sử dụng các phương thức như Array.sort().

Dưới đây là một giải pháp lấy cảm hứng từ when.js 's when.all()phương pháp mà địa chỉ những vấn đề này:

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

Bây giờ bạn có thể chỉ cần chuyển vào một loạt các lời hứa / hoãn lại và nhận lại một loạt các đối tượng được giải quyết / bị từ chối trong cuộc gọi lại của bạn, như vậy:

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

6
Bạn có thể muốn sử dụng giải quyếtWith và từ chối Với chỉ để bạn nhận được cùng một bản gốc hoãn lại như 'this' deferred.resolveWith (this, [Array.prototype.slice.call (argument)]) v.v.
Jamie Pate

1
Chỉ có một vấn đề nhỏ với mã của bạn, khi chỉ có một phần tử trong mảng, mảng kết quả trả về kết quả đó, thay vì một mảng có một phần tử duy nhất (sẽ phá vỡ mã mong đợi một mảng). Để sửa nó, sử dụng chức năng này var toArray = function (args) { return deferreds.length > 1 ? $.makeArray(args) : [args]; }thay vì Array.prototype.slice.call.
Luân Nico

Hừm, điều này dường như không bắt được bất kỳ 404 nào.
t.mikael.d

Tìm thấy lý do, .fail nên là .reject thay vào đó - để nó có thể bắt được 404.
t.mikael.d

38

Bạn có thể áp dụng whenphương thức cho mảng của mình:

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

$.when.apply($, arr);

Làm thế nào để bạn làm việc với một mảng của Trì hoãn jQuery?


Tôi thực sự đã thấy câu hỏi đó nhưng tôi đoán tất cả các chi tiết bổ sung trong câu hỏi đó đã khiến câu trả lời cho vấn đề của tôi (nằm ngay trong đó) bay ngay trên đầu tôi.
adamjford

1
@adamjford, nếu nó làm bạn cảm thấy tốt hơn, tôi thấy câu hỏi của bạn dễ tiêu thụ hơn (và đầu tiên trên tìm kiếm Google cụ thể của tôi cho vấn đề chính xác này).
patridge

@patridge: Rất vui khi biết nó đã giúp bạn!
adamjford

Đây là một câu trả lời tuyệt vời, nhưng tôi không rõ điều này áp dụng cho ví dụ trong câu hỏi ban đầu như thế nào. Sau khi tham khảo câu hỏi được liên kết, rõ ràng dòng "$ .when (hoãn lại) .done (function () {" nên được thay đổi thành "$ .when.apply ($, hoãn lại) .done (function () { ". Phải không?
Garland Pope

7

Khi gọi nhiều cuộc gọi AJAX song song, bạn có hai tùy chọn để xử lý các phản hồi tương ứng.

  1. Sử dụng cuộc gọi AJAX đồng bộ / cuộc gọi khác / không được đề xuất
  2. Sử dụng Promises'mảng và $.whenchấp nhận promises và gọi lại của nó .doneđược gọi khi tất cả các promises được trả về thành công với các phản hồi tương ứng.

Thí dụ

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
câu trả lời của bạn phản ứng thái quá, và bạn cũng chỉnh sửa tiêu đề câu hỏi. OP đã biết cách thực hiện các cuộc gọi AJAX và nhận được một loạt các đối tượng bị trì hoãn. Điểm duy nhất của câu hỏi là làm thế nào để vượt qua mảng đó $.when.
Alnitak

5
Tôi nghĩ rằng giải thích chi tiết bằng ví dụ sẽ tốt hơn, với các tùy chọn có sẵn. Và tôi không nghĩ rằng downvote là cần thiết.
vinayakj

2
downvote là cho 1. thậm chí đề xuất đồng bộ hóa (mặc dù không khuyến nghị) 2. mã chất lượng kém trong các ví dụ (bao gồm cả for ... intrên một mảng?!)
Alnitak

1
1. Đồng ý, lẽ ra phải có (not recommended)2. Không đồng ý - không sao for ... invì mảng chỉ chứa những thuộc tính cần (không có thuộc tính bổ sung). thanx anyways
vinayakj

1
re: 2 - vấn đề là nó có thể bị sao chép bởi những người khác không thể đảm bảo điều đó hoặc đã đủ ngu ngốc để thêm vào Array.prototype. Trong mọi trường hợp, đối với mã không quan trọng về hiệu năng, tốt hơn là sử dụng .mapthay vì vòng lặp for/ push, ví dụ: var promises = capitalCities.map(ajaxRequest); $.when.apply($, promises).then(fillCountryCapitals)công việc được thực hiện.
Alnitak

6

Thay thế đơn giản, không yêu cầu $.when.applyhoặc không array, bạn có thể sử dụng mẫu sau để tạo một lời hứa duy nhất cho nhiều lời hứa song song:

promise = $.when(promise, anotherPromise);

ví dụ

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

Ghi chú:

  • Tôi đã tìm ra điều này sau khi thấy ai đó hứa hẹn chuỗi, sử dụng promise = promise.then(newpromise)
  • Nhược điểm là nó tạo ra các đối tượng hứa hẹn bổ sung phía sau hậu trường và bất kỳ tham số nào được truyền ở cuối không hữu ích lắm (vì chúng được lồng bên trong các đối tượng bổ sung). Đối với những gì bạn muốn mặc dù nó ngắn và đơn giản.
  • Ưu điểm là nó không yêu cầu quản lý mảng hoặc mảng.

2
Sửa lỗi cho tôi nếu tôi sai, nhưng cách tiếp cận của bạn có hiệu quả lồng nhau $ .when ($ .when ($ .when (...))) để bạn kết thúc đệ quy lồng sâu 10 cấp nếu có 10 lần lặp. Điều này dường như không song song vì bạn phải đợi mỗi cấp độ trả lại lời hứa lồng nhau của trẻ trước khi nó có thể trả lại lời hứa của chính mình - Tôi nghĩ rằng cách tiếp cận mảng trong câu trả lời được chấp nhận sẽ sạch hơn vì nó sử dụng hành vi tham số linh hoạt được tích hợp vào phương thức $ .when ().
Anthony McLin

@AnthonyMcLin: điều này nhằm cung cấp một giải pháp thay thế đơn giản hơn cho mã hóa, hiệu năng không tốt hơn (không đáng kể với hầu hết mã hóa Async), như được thực hiện với then()các cuộc gọi theo cách tương tự. Hành vi với $.whenlà hành động như nó là song song (không bị xiềng xích). Vui lòng thử trước khi bỏ đi một giải pháp thay thế hữu ích vì nó hoạt động :)
Mã hóa

2
@Alnitak: Ngựa cho các khóa học. Bạn chắc chắn có quyền đưa ra ý kiến, nhưng rõ ràng bạn đã không sử dụng điều này cho mình. Ý kiến ​​riêng của tôi dựa trên việc sử dụng thực tế của kỹ thuật này. Nó hoạt động và có công dụng, vậy tại sao lại ném ra một công cụ từ hộp công cụ dựa trên các cường điệu như "vô số cảnh báo" (một) và "không giải quyết được gì" (không đúng - nó loại bỏ việc xử lý mảng và đơn giản hóa chuỗi lời hứa song song các giá trị là không cần thiết, mà như bạn nên biết là hiếm khi được sử dụng trong các trường hợp xử lý song song). Downvote được cho là cho "câu trả lời này không hữu ích" :)
Mã hóa

1
Xin chào @GoneCoding. Tôi có thể yêu cầu bạn không thêm bình luận bỏ phiếu vào câu trả lời của bạn không? Điều đó phù hợp với các bình luận, nhưng nếu không thì đó là tiếng ồn làm mất tập trung vào nội dung tốt. Cảm ơn.
buộc dây

1
@halfer: Tôi không đăng thêm nữa nhưng tôi cảm thấy khó chịu vì sự thiếu hiểu biết được hiển thị cho bất cứ điều gì ban đầu. Giữ tất cả các ý tưởng mới cho bản thân tôi ngày hôm nay :)
Mã hóa

4

Tôi muốn đề xuất một cái khác bằng cách sử dụng $ .each:

  1. Chúng tôi có thể khai báo hàm ajax như:

    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. Một phần mã nơi chúng tôi tạo mảng hàm với ajax để gửi:

    var arrayOfFn = [];
    for (var i = 0; i < someDataArray.length; i++) {
        var ajaxFnForArray = new ajaxFn(someDataArray[i]);
        arrayOfFn.push(ajaxFnForArray);
    }
  3. Và gọi các chức năng với việc gửi ajax:

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

1

Nếu bạn đang dịch mã và có quyền truy cập vào ES6, bạn có thể sử dụng cú pháp trải rộng áp dụng cụ thể từng mục có thể lặp lại của một đối tượng như một đối số riêng biệt, chỉ là cách $.when()cần nó.

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

Liên kết MDN - Cú pháp lây lan


0

Nếu bạn đang sử dụng angularJS hoặc một số biến thể của thư viện Q hứa, thì bạn có một .all()phương pháp giải quyết vấn đề chính xác này.

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

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

xem API đầy đủ:

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

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


4
Điều này là hoàn toàn không liên quan.
Benjamin Gruenbaum

@BenjaminGruenbaum Làm sao vậy? Tất cả các thư viện hứa hẹn javascript đều chia sẻ một API tương tự và không có gì sai khi hiển thị các triển khai khác nhau. Tôi đã truy cập trang này để tìm câu trả lời cho góc cạnh và tôi nghi ngờ nhiều người dùng khác sẽ truy cập trang này và không nhất thiết phải ở trong một môi trường duy nhất.
mastaBlasta

2
Cụ thể, vì các lời hứa của jQuery không chia sẻ API này, nên điều này hoàn toàn không phù hợp như một câu trả lời trên Stack Overflow - có những câu trả lời tương tự cho Angular và bạn có thể hỏi ở đó. (Chưa kể, bạn nên .mapở đây nhưng oh tốt).
Benjamin Gruenbaum

0

Tôi đã có một trường hợp rất giống với việc tôi đã đăng trong mỗi vòng lặp và sau đó thiết lập đánh dấu html trong một số trường từ các số nhận được từ ajax. Sau đó, tôi cần phải thực hiện tổng các giá trị (hiện được cập nhật) của các trường này và đặt trong một trường tổng.

Do đó, vấn đề là tôi đã cố gắng tính tổng tất cả các số nhưng chưa có dữ liệu nào quay trở lại từ các cuộc gọi ajax async. Tôi cần phải hoàn thành chức năng này trong một vài chức năng để có thể sử dụng lại mã. Hàm ngoài của tôi chờ dữ liệu trước khi tôi đi và thực hiện một số nội dung với DOM được cập nhật đầy đủ.

    // 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;
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.