Yêu cầu Ajax không đồng bộ song song sử dụng jQuery


76

Tôi muốn cập nhật một trang dựa trên kết quả của nhiều yêu cầu ajax / json. Sử dụng jQuery, tôi có thể "xâu chuỗi" các lệnh gọi lại, như ví dụ rút gọn rất đơn giản này:

$.getJSON("/values/1", function(data) {
  // data = {value: 1}
  var value_1 = data.value;

  $.getJSON("/values/2", function(data) {
    // data = {value: 42}
    var value_2 = data.value;

    var sum = value_1 + value_2;

    $('#mynode').html(sum);
  });

});

Tuy nhiên, điều này dẫn đến các yêu cầu được thực hiện theo thứ tự. Tôi thích một cách để thực hiện các yêu cầu song song và thực hiện cập nhật trang sau khi tất cả đã hoàn tất. Có cách nào để làm điều này?

Câu trả lời:


107

Hãy thử giải pháp này, có thể hỗ trợ bất kỳ số lượng truy vấn song song cụ thể nào:

var done = 4; // number of total requests
var sum = 0;

/* Normal loops don't create a new scope */
$([1,2,3,4,5]).each(function() {
  var number = this;
  $.getJSON("/values/" + number, function(data) {
    sum += data.value;
    done -= 1;
    if(done == 0) $("#mynode").html(sum);
  });
});

33
Nếu tôi không nhầm, bạn là tác giả của 'jQuery In Action'?
karim79

1
Cuốn sách tuyệt vời! Tên của bạn đã làm chuông báo động trong đầu tôi!
karim79

2
Tôi đã chọn một thứ tương tự như của bạn và của agilefall: var results = {}; var yêu cầu = 0; var urls = ["giá trị / 1", "giá trị / 2", "giá trị / 3"]; $ .each (url, function (url) {$ .getJSON (url, function (data) {results [url] = data.value; ++ yêu cầu; nếu (yêu cầu == 3) {$ ('# mynode') .html (results [urls [0]] / results [urls [1]] * results [urls [2]]);}});});
Paul

Tôi đã làm một cái gì đó tương tự như thế này. Cuối cùng, tôi đã củng cố yêu cầu của mình. Nhưng thật tốt nếu biết cách làm điều này để đề phòng. Tôi đã hiển thị một thanh tiến trình, thanh này hoạt động tốt trong trường hợp này vì mã được hướng dẫn gọi lại. Chỉ cần sử dụng 100 * ((4-done) / 4) trong trường hợp này cho phần trăm hoàn thành.
Nosredna

1
Có vẻ như mã bên trong "if" có thể được thực thi nhiều lần. Ngoài ra, "done - = 1" có phải là nguyên tử không?
Gzorg

119

jQuery $ .when ()$ .done () là chính xác những gì bạn cần:

$.when($.ajax("/page1.php"), $.ajax("/page2.php"))
  .then(myFunc, myFailure);

+1 Tôi đã nghe ở đâu đó rằng những lời hứa không được sáng tác tốt ... Rõ ràng là tôi cần phải quên điều đó đi!
Daniel Earwicker

Không xúc phạm, nhưng câu trả lời này không vượt trội hơn nhiều so với câu trả lời của @ yehuda-katz sao? (Cho rằng điều này hoạt động)
Herbert

5
Câu trả lời này không rõ ràng về cách truy cập dữ liệu sau khi các lệnh gọi ajax hoàn tất. Những gì đang được chuyển đến myFunc và làm cách nào để truy cập các cuộc gọi?
Martin Burch

2
ví dụ với myFunc và myFailure => codepen.io/jacobgoh101/pen/YaJOzx?editors=0010
Jacob Goh

9

Đây là nỗ lực của tôi để giải quyết trực tiếp câu hỏi của bạn

Về cơ bản, bạn chỉ cần xây dựng và ngăn xếp cuộc gọi AJAX, thực thi tất cả chúng và một hàm được cung cấp được gọi khi hoàn thành tất cả các sự kiện - đối số được cung cấp là một mảng kết quả từ tất cả các yêu cầu ajax được cung cấp.

Rõ ràng đây là mã ban đầu - bạn có thể hiểu rõ hơn về tính linh hoạt của mã này.

<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
<script type="text/javascript">

var ParallelAjaxExecuter = function( onComplete )
{
  this.requests = [];
  this.results = [];
  this.onComplete = onComplete; 
}

ParallelAjaxExecuter.prototype.addRequest = function( method, url, data, format )
{
  this.requests.push( {
      "method"    : method
    , "url"       : url
    , "data"      : data
    , "format"    : format
    , "completed" : false
  } )
}

ParallelAjaxExecuter.prototype.dispatchAll = function()
{
  var self = this;
  $.each( self.requests, function( i, request )
    {
    request.method( request.url, request.data, function( r )
    {
      return function( data )
      {
        console.log
        r.completed = true;
        self.results.push( data );
        self.checkAndComplete();
      }
    }( request ) )
  } )
}

ParallelAjaxExecuter.prototype.allRequestsCompleted = function()
{
  var i = 0;
  while ( request = this.requests[i++] )
  {
    if ( request.completed === false )
    {
      return false;
    }
  }
  return true;
},

ParallelAjaxExecuter.prototype.checkAndComplete = function()
{
  if ( this.allRequestsCompleted() )
  {
    this.onComplete( this.results );
  }
}

var pe = new ParallelAjaxExecuter( function( results )
{
  alert( eval( results.join( '+' ) ) );
} );

pe.addRequest( $.get, 'test.php', {n:1}, 'text' );
pe.addRequest( $.get, 'test.php', {n:2}, 'text' );
pe.addRequest( $.get, 'test.php', {n:3}, 'text' );
pe.addRequest( $.get, 'test.php', {n:4}, 'text' );

pe.dispatchAll();

</script>

đây là test.php

<?php

echo pow( $_GET['n'], 2 );

?>

9

Cập nhật: Theo câu trả lời được đưa ra bởi Yair Leviel, câu trả lời này đã lỗi thời. Sử dụng thư viện lời hứa, như jQuery.when () hoặc Q.js.


Tôi đã tạo một giải pháp cho mục đích chung dưới dạng phần mở rộng jQuery. Có thể sử dụng một số tinh chỉnh để làm cho nó tổng quát hơn, nhưng nó phù hợp với nhu cầu của tôi. Ưu điểm của kỹ thuật này so với các kỹ thuật khác trong bài đăng này vào thời điểm viết bài này là có thể sử dụng bất kỳ kiểu xử lý không đồng bộ nào với lệnh gọi lại.

Lưu ý: Tôi sẽ sử dụng phần mở rộng Rx cho JavaScript thay vì phần mở rộng này nếu tôi nghĩ rằng khách hàng của mình sẽ ổn với việc phụ thuộc vào thư viện chưa-khác-bên thứ ba :)

// jQuery extension for running multiple async methods in parallel
// and getting a callback with all results when all of them have completed.
//
// Each worker is a function that takes a callback as its only argument, and
// fires up an async process that calls this callback with its result.
//
// Example:
//      $.parallel(
//          function (callback) { $.get("form.htm", {}, callback, "html"); },
//          function (callback) { $.post("data.aspx", {}, callback, "json"); },
//          function (formHtml, dataJson) { 
//              // Handle success; each argument to this function is 
//              // the result of correlating ajax call above.
//          }
//      );

(function ($) {

    $.parallel = function (anyNumberOfWorkers, allDoneCallback) {

    var workers = [];
    var workersCompleteCallback = null;

    // To support any number of workers, use "arguments" variable to
    // access function arguments rather than the names above.
    var lastArgIndex = arguments.length - 1;
    $.each(arguments, function (index) {
        if (index == lastArgIndex) {
            workersCompleteCallback = this;
        } else {
            workers.push({ fn: this, done: false, result: null });
        }
    });

    // Short circuit this edge case
    if (workers.length == 0) {
        workersCompleteCallback();
        return;
    }

    // Fire off each worker process, asking it to report back to onWorkerDone.
    $.each(workers, function (workerIndex) {
        var worker = this;
        var callback = function () { onWorkerDone(worker, arguments); };
        worker.fn(callback);
    });

    // Store results and update status as each item completes.
    // The [0] on workerResultS below assumes the client only needs the first parameter
    // passed into the return callback. This simplifies the handling in allDoneCallback,
    // but may need to be removed if you need access to all parameters of the result.
    // For example, $.post calls back with success(data, textStatus, XMLHttpRequest).  If
    // you need textStatus or XMLHttpRequest then pull off the [0] below.
    function onWorkerDone(worker, workerResult) {
        worker.done = true;
        worker.result = workerResult[0]; // this is the [0] ref'd above.
        var allResults = [];
        for (var i = 0; i < workers.length; i++) {
            if (!workers[i].done) return;
            else allResults.push(workers[i].result);
        }
        workersCompleteCallback.apply(this, allResults);
    }
};

})(jQuery);

2
Tôi đã sử dụng cái này và nó hoạt động tốt! ... nhưng tôi có một cải tiến: nếu bạn sửa đổi lệnh gọi lại cuối cùng để sử dụng "áp dụng", thì bạn sẽ nhận được các đối số riêng biệt cho lệnh gọi lại của mình thay vì một danh sách các đối số: tức là workerCompleteCallback.apply (this, allResults);
Nick Perkins

9

Chạy nhiều yêu cầu AJAX song song

Khi làm việc với các API, đôi khi bạn cần đưa ra nhiều yêu cầu AJAX tới các điểm cuối khác nhau. Thay vì đợi một yêu cầu hoàn thành trước khi đưa ra yêu cầu tiếp theo, bạn có thể tăng tốc mọi thứ với jQuery bằng cách yêu cầu dữ liệu song song, bằng cách sử dụng $.when()hàm của jQuery :

JS

$.when($.get('1.json'), $.get('2.json')).then(function(r1, r2){
   console.log(r1[0].message + " " + r2[0].message);
});

Hàm gọi lại được thực thi khi cả hai yêu cầu GET này kết thúc thành công. $.when()nhận các lời hứa được trả về bởi hai $.get()cuộc gọi và xây dựng một đối tượng lời hứa mới. Các r1r2đối số của lệnh gọi lại là các mảng, có các phần tử đầu tiên chứa các phản hồi của máy chủ.


7

CẬP NHẬT Và hai năm sau, điều này có vẻ điên rồ vì câu trả lời được chấp nhận đã thay đổi thành một thứ tốt hơn nhiều! (Mặc dù vẫn không tốt bằng câu trả lời của Yair Leviel bằng cách sử dụng jQuery when)

18 tháng sau, tôi vừa đánh một thứ tương tự. Tôi có một nút làm mới và tôi muốn nội dung cũ fadeOutrồi đến nội dung mới fadeIn. Nhưng tôi cũng cần getnội dung mới. Các fadeOutgetlà không đồng bộ, nhưng sẽ rất lãng phí thời gian để chạy chúng theo thứ tự.

Những gì tôi làm thực sự giống với câu trả lời được chấp nhận, ngoại trừ ở dạng một hàm có thể tái sử dụng. Đức tính chính của nó là nó ngắn hơn nhiều so với các đề xuất khác ở đây.

var parallel = function(actions, finished) {

  finishedCount = 0;
  var results = [];

  $.each(actions, function(i, action) {

    action(function(result) {

      results[i] = result;
      finishedCount++;

      if (finishedCount == actions.length) {
        finished(results);
      }
    });
  });
};

Bạn truyền cho nó một mảng các hàm để chạy song song. Mỗi hàm nên chấp nhận một hàm khác mà nó chuyển kết quả (nếu có). parallelsẽ cung cấp chức năng đó.

Bạn cũng chuyển cho nó một hàm được gọi khi tất cả các hoạt động đã hoàn thành. Điều này sẽ nhận được một mảng có tất cả các kết quả. Vì vậy, ví dụ của tôi là:

refreshButton.click(function() {

  parallel([
       function(f) { 
         contentDiv.fadeOut(f); 
       },
       function(f) { 
         portlet.content(f); 
       },
     ], 
     function(results) {
      contentDiv.children().remove();
      contentDiv.append(results[1]);
      contentDiv.fadeIn();
  });
});

Vì vậy, khi nút làm mới của tôi được nhấp, tôi khởi chạy fadeOuthiệu ứng của jQuery và cũng là portlet.contentchức năng của riêng tôi (thực hiện không đồng bộ get, xây dựng một chút nội dung mới và chuyển nó vào), và sau đó khi cả hai hoàn tất, tôi xóa nội dung cũ, nối kết quả của chức năng thứ hai (có trong results[1]) và fadeInnội dung mới.

fadeOutkhông chuyển bất cứ thứ gì đến hàm hoàn thành của nó, results[0]có lẽ là chứa undefined, vì vậy tôi bỏ qua nó. Nhưng nếu bạn có ba phép toán với kết quả hữu ích, chúng sẽ từng vị trí vào resultsmảng, theo thứ tự bạn đã chuyển các hàm.


5

bạn có thể làm một cái gì đó như thế này

var allData = []
$.getJSON("/values/1", function(data) {
    allData.push(data);
    if(data.length == 2){
      processData(allData) // where process data processes all the data
    }
});

$.getJSON("/values/2", function(data) {
    allData.push(data);
    if(data.length == 2){
        processData(allData) // where process data processes all the data
    }
});

var processData = function(data){
     var sum = data[0] + data[1]
     $('#mynode').html(sum);
}

3

Đây là cách triển khai bằng mbostock / queue :

queue()
  .defer(function(callback) {
    $.post('/echo/json/', {json: JSON.stringify({value: 1}), delay: 1}, function(data) {
      callback(null, data.value);
    });
  })
  .defer(function(callback) {
    $.post('/echo/json/', {json: JSON.stringify({value: 3}), delay: 2}, function(data) {
      callback(null, data.value);
    });
  })
  .awaitAll(function(err, results) {
    var result = results.reduce(function(acc, value) {
      return acc + value;
    }, 0);
    console.log(result);
  });

Fiddle liên quan: http://jsfiddle.net/MdbW2/


3

Với phần mở rộng sau của JQuery (có thể được viết như một hàm độc lập, bạn có thể thực hiện việc này:

$.whenAll({
    val1: $.getJSON('/values/1'),
    val2: $.getJSON('/values/2')
})
    .done(function (results) {
        var sum = results.val1.value + results.val2.value;

        $('#mynode').html(sum);
    });

Phần mở rộng JQuery (1.x) whenAll ():

$.whenAll = function (deferreds) {
    function isPromise(fn) {
        return fn && typeof fn.then === 'function' &&
            String($.Deferred().then) === String(fn.then);
    }
    var d = $.Deferred(),
        keys = Object.keys(deferreds),
        args = keys.map(function (k) {
            return $.Deferred(function (d) {
                var fn = deferreds[k];

                (isPromise(fn) ? fn : $.Deferred(fn))
                    .done(d.resolve)
                    .fail(function (err) { d.reject(err, k); })
                ;
            });
        });

    $.when.apply(this, args)
        .done(function () {
            var resObj = {},
                resArgs = Array.prototype.slice.call(arguments);
            resArgs.forEach(function (v, i) { resObj[keys[i]] = v; });
            d.resolve(resObj);
        })
        .fail(d.reject);

    return d;
};

Xem ví dụ về jsbin: http://jsbin.com/nuxuciwabu/edit?js,console


3

Giải pháp chuyên nghiệp nhất đối với tôi là sử dụng async.js và Array.reduce như sau:

        async.map([1, 2, 3, 4, 5], function (number, callback) {
            $.getJSON("/values/" + number, function (data) {
                callback(null, data.value);
            });
        }, function (err, results) {
            $("#mynode").html(results.reduce(function(previousValue, currentValue) {
                return previousValue + currentValue;
            }));
        });

1

Nếu kết quả của một yêu cầu phụ thuộc vào yêu cầu kia, bạn không thể đặt chúng song song.


1
Một cái không phụ thuộc vào cái kia, nhưng kết quả cuối cùng phụ thuộc vào mỗi cái được hoàn thành.
Paul

1
Vì đó là một phép toán đơn giản giữa dữ liệu mà bạn nhận lại, nên có, bạn có thể sử dụng một biến ngoài phạm vi để theo dõi dữ liệu được thêm vào. Nhưng trong hầu hết các trường hợp, đây không phải là giải pháp có giá trị cho các yêu cầu song song phụ thuộc vào dữ liệu của nhau.
Luca Matteis

1

Dựa trên câu trả lời của Yair. Bạn có thể xác định động các lời hứa ajax.

var start = 1; // starting value
var len = 2; // no. of requests

var promises = (new Array(len)).fill().map(function() {
    return $.ajax("/values/" + i++);
});

$.when.apply($, promises)
  .then(myFunc, myFailure);

0

Giả sử bạn có một mảng tên tệp.

var templateNameArray=["test.html","test2.html","test3.html"];

htmlTemplatesLoadStateMap={};
var deffereds=[];
  for (var i = 0; i < templateNameArray.length; i++)
       {
        if (!htmlTemplatesLoadStateMap[templateNameArray[i]]) 
            {         
              deferreds.push($.get("./Content/templates/" +templateNameArray[i], 

                  function (response, status, xhr) {
                      if (status == "error") { } 
                        else {
                                $("body").append(response);
                               }
                         }));             
htmlTemplatesLoadStateMap[templateNameArray[i]] = true;
                       }
                  }
                                      $.when.all(deferreds).always(function(resultsArray) {   yourfunctionTobeExecuted(yourPayload);
                                });
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.