Làm cách nào để chờ tập hợp các hàm gọi lại không đồng bộ?


95

Tôi có mã trông giống như thế này trong javascript:

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

Sau khi TẤT CẢ các lệnh gọi không đồng bộ đó được thực hiện, tôi muốn tính toán tối thiểu trên tất cả các mảng.

Làm thế nào tôi có thể chờ đợi cho tất cả chúng?

Ý tưởng duy nhất của tôi lúc này là có một mảng boolean được gọi là done và đặt done [i] thành true trong hàm gọi lại thứ i, sau đó nói while (không phải tất cả đều được thực hiện) {}

chỉnh sửa: Tôi cho rằng một giải pháp khả thi, nhưng không tốt, sẽ là chỉnh sửa mảng đã hoàn thành trong mỗi lần gọi lại, sau đó gọi một phương thức nếu tất cả những việc khác được thực hiện được đặt từ mỗi lần gọi lại, do đó, lần gọi lại cuối cùng để hoàn thành sẽ gọi phương thức tiếp tục.

Cảm ơn trước.


1
Khi không đồng bộ có nghĩa là bạn đang đợi một yêu cầu Ajax hoàn tất?
Peter Aron Zentai

6
Lưu ý, while (not all are done) { }sẽ không hoạt động. Trong khi bạn đang bận chờ đợi, không có cuộc gọi lại nào của bạn có thể chạy.
cHao

Đúng. Tôi đang chờ một lệnh gọi không đồng bộ đến một API bên ngoài để trả về để nó kích hoạt các phương thức gọi lại. Vâng Chao, tôi nhận ra rằng, đó là lý do tôi yêu cầu để được giúp đỡ ở đây: D
codersarepeople

Bạn có thể thử cái này: github.com/caolan/async Bộ chức năng tiện ích không đồng bộ rất hay.
Paul Greyson

Câu trả lời:


191

Bạn chưa nói rõ lắm về mã của mình, vì vậy tôi sẽ tạo một kịch bản. Giả sử bạn có 10 lệnh gọi ajax và bạn muốn tích lũy kết quả từ 10 lệnh gọi ajax đó và sau đó khi tất cả chúng đã hoàn thành, bạn muốn làm điều gì đó. Bạn có thể làm như vậy bằng cách tích lũy dữ liệu trong một mảng và theo dõi thời điểm kết thúc cuối cùng:

Bộ đếm thủ công

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

Lưu ý: việc xử lý lỗi rất quan trọng ở đây (không được hiển thị vì nó dành riêng cho cách bạn thực hiện cuộc gọi ajax của mình). Bạn sẽ muốn nghĩ về cách bạn sẽ xử lý trường hợp khi một cuộc gọi ajax không bao giờ hoàn thành, có lỗi hoặc bị kẹt trong một thời gian dài hoặc hết thời gian sau một thời gian dài.


jQuery Hứa hẹn

Thêm vào câu trả lời của tôi vào năm 2014. Ngày nay, các lời hứa thường được sử dụng để giải quyết loại vấn đề này vì jQuery's $.ajax()đã trả về một lời hứa và $.when()sẽ cho bạn biết khi một nhóm lời hứa được giải quyết xong và sẽ thu thập kết quả trả về cho bạn:

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

Lời hứa tiêu chuẩn ES6

Như đã chỉ rõ trong câu trả lời của kba : nếu bạn có một môi trường với các hứa hẹn gốc được tích hợp sẵn (trình duyệt hiện đại hoặc node.js hoặc sử dụng babeljs transpile hoặc sử dụng đa điền vào lời hứa), thì bạn có thể sử dụng các hứa hẹn do ES6 chỉ định. Xem bảng này để được hỗ trợ trình duyệt. Hứa hẹn được hỗ trợ trong hầu hết các trình duyệt hiện tại, ngoại trừ IE.

Nếu doAjax()trả về một lời hứa, thì bạn có thể thực hiện điều này:

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

Nếu bạn cần thực hiện một hoạt động không đồng bộ hóa không có lời hứa thành một hoạt động trả về một lời hứa, bạn có thể "quảng bá" nó như sau:

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

Và, sau đó sử dụng mẫu trên:

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

Lời hứa của Bluebird

Nếu bạn sử dụng một thư viện giàu tính năng hơn, chẳng hạn như thư viện Bluebird hứa hẹn , thì nó có một số chức năng bổ sung được tích hợp sẵn để làm cho việc này dễ dàng hơn:

 var doAjax = Promise.promisify(someAsync);
 var someData = [...]
 Promise.map(someData, doAjax).then(function(results) {
     // all ajax results here
 }, function(err) {
     // some error here
 });

4
@kba - Tôi sẽ không gọi chính xác câu trả lời này là lỗi thời vì tất cả các kỹ thuật vẫn có thể áp dụng, đặc biệt nếu bạn đã sử dụng jQuery cho Ajax. Nhưng, tôi đã cập nhật nó theo một số cách để bao gồm các lời hứa ban đầu.
jfriend00

ngày nay có một giải pháp sạch hơn nhiều mà thậm chí không cần jquery. Tôi đang làm điều đó với FetchAPI và Promises
philx_x 14/04/16

@philx_x - Bạn đang làm gì về hỗ trợ của IE và Safari?
jfriend00

@ jfriend00 github đã tạo polyfill github.com/github/fetch . Hoặc tôi không chắc liệu babel có hỗ trợ tìm nạp hay không. babeljs.io
philx_x

@philx_x - Nghĩ vậy. Bạn cần một thư viện polyfill để sử dụng tìm nạp ngày nay. Bỏ qua một chút bình luận của bạn về việc tránh thư viện ajax. Tìm nạp rất hay, nhưng còn nhiều năm nữa mới có thể sử dụng nó mà không có polyfill. Nó thậm chí chưa có trong phiên bản mới nhất của tất cả các trình duyệt. Làm, nó không thực sự thay đổi bất cứ điều gì trong câu trả lời của tôi. Tôi đã có một doAjax()lời hứa trả lại như một trong những lựa chọn. Điều tương tự như fetch().
jfriend00

17

Đăng ký từ năm 2015: Chúng tôi hiện có các hứa hẹn gốc trong trình duyệt gần đây nhất (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 và trình duyệt Android 4.4.4 và iOS Safari 8.4, nhưng không có Internet Explorer, Opera Mini và các phiên bản cũ hơn của Android).

Nếu chúng ta muốn thực hiện 10 hành động không đồng bộ và nhận được thông báo khi chúng đã hoàn tất, chúng ta có thể sử dụng bản gốc Promise.allmà không cần bất kỳ thư viện bên ngoài nào:

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

2
Promises.all()nên được Promise.all().
jfriend00

1
Câu trả lời của bạn cũng cần tham khảo bạn có thể sử dụng trình duyệt nào Promise.all()trong đó không bao gồm phiên bản IE hiện tại.
jfriend00

10

Bạn có thể sử dụng đối tượng Deferred của jQuery cùng với phương thức 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
});

7
Câu hỏi không được gắn thẻ jQuerythường có nghĩa là OP không muốn câu trả lời jQuery.
jfriend00

8
@ jfriend00 Tôi không muốn phát minh lại bánh xe khi nó đã được tạo ra trong jQuery
Paul

4
@ Paul nên thay sau đó tái phát minh ra bánh xe bao gồm 40KB lại rác để làm một cái gì đó đơn giản (deferreds)
Raynos

2
Nhưng không phải ai cũng có thể hoặc muốn sử dụng jQuery và tùy chỉnh ở đây trên SO là bạn cho biết điều đó bằng cách bạn gắn thẻ câu hỏi của mình bằng jQuery hay không.
jfriend00

4
Ví dụ này gọi là $ .when là không chính xác. Để đợi một mảng hoãn / hứa, bạn cần sử dụng $ .when.apply ($, promise) .then (function () {/ * do things * /}).
danw

9

Bạn có thể mô phỏng nó như thế này:

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

thì mỗi lệnh gọi không đồng bộ thực hiện điều này:

countDownLatch.count++;

trong khi trong mỗi lệnh gọi lại asynch ở cuối phương thức, bạn thêm dòng này:

countDownLatch.check();

Nói cách khác, bạn mô phỏng chức năng chốt đếm ngược.


Trong 99% tất cả các trường hợp sử dụng, Promise là cách để đi nhưng tôi thích câu trả lời này vì nó minh họa một phương pháp để quản lý mã Async trong các tình huống mà đa điền Promise lớn hơn thì JS sử dụng nó!
Sukima

6

Đây là cách gọn gàng nhất theo ý kiến ​​của tôi.

Promise.all

FetchAPI

(vì một số lý do Array.map không hoạt động bên trong các hàm .then đối với tôi. Nhưng bạn có thể sử dụng .forEach và [] .concat () hoặc một cái gì đó tương tự)

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

1
Tôi nghĩ rằng điều này cần phải được return responses.map(response => { return response.json(); }), hoặc return responses.map(response => response.json()).

1

Sử dụng thư viện luồng điều khiển như 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)
})
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.