Câu hỏi Làm thế nào tôi có thể chờ đợi để thiết lập các chức năng gọi lại không đồng bộ?


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 cuộc gọi async được thực hiện, tôi muốn tính toán min 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 ngay bây giờ là có một loạt các boolean được gọi là xong, và thiết lập xong [i] thành true trong hàm gọi lại thứ i, sau đó nói trong khi (không phải tất cả được thực hiện) {}

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

Cảm ơn trước.


76
2018-04-04 02:14


gốc


Trên async bạn có nghĩa là chờ đợi một yêu cầu Ajax để hoàn thành? - Peter Aron Zentai
Chú thích, while (not all are done) { } sẽ không hoạt động. Trong khi bạn đang chờ đợi, không có cuộc gọi lại nào của bạn có thể chạy. - cHao
Vâng. Tôi đang chờ một cuộc gọi async đến một API bên ngoài để trở về để nó sẽ kích hoạt các phương thức gọi lại. Vâng, tôi nhận ra rằng, đó là lý do tại sao tôi yêu cầu giúp đỡ ở đây: D - codersarepeople
Bạn có thể thử điều này: github.com/caolan/async Rất tốt đẹp thiết lập các chức năng tiện ích async. - Paul Greyson


Các câu trả lời:


Bạn đã không được rất cụ thể với mã của bạn, vì vậy tôi sẽ tạo nên một kịch bản. Giả sử bạn có 10 cuộc gọi ajax và bạn muốn tích lũy kết quả từ 10 cuộc gọi ajax đó và sau đó khi họ hoàn tất, bạn muốn làm điều gì đó. Bạn có thể làm điều đó như thế nà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 dữ liệu 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 ý: xử lý lỗi rất quan trọng ở đây (không được hiển thị vì nó chỉ rõ cách bạn thực hiện cuộc gọi ajax). Bạn sẽ muốn suy nghĩ về cách bạn sẽ xử lý các trường hợp khi một cuộc gọi ajax không bao giờ hoàn thành, hoặc với một lỗi hoặc bị mắc kẹt trong một thời gian dài hoặc thời gian ra sau một thời gian dài.


jQuery Promises

Thêm vào câu trả lời của tôi trong năm 2014. Những ngày này, lời hứa thường được sử dụng để giải quyết loại vấn đề này kể từ khi jQuery $.ajax() đã trả lời một lời hứa và $.when() sẽ cho bạn biết khi một nhóm các lời hứa được giải quyết 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
});

ES6 Standard Promises

Theo quy định trong câu trả lời của kba: nếu bạn có môi trường với lời hứa 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 tập tin babeljs transpile hoặc sử dụng lời hứa polyfill), thì bạn có thể sử dụng lời hứa được chỉ định ES6. Xem cái bàn này để hỗ trợ trình duyệt. Lời hứa được hỗ trợ khá nhiều trong tất cả 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, sau đó bạn có thể làm đ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 phải thực hiện một hoạt động không đồng bộ không hứa hẹn thành một hoạt động trả về lời hứa, bạn có thể "quảng cáo" 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 Bluebird

Nếu bạn sử dụng thư viện giàu tính năng hơn, chẳng hạn như Thư viện lời hứa Bluebird, sau đó nó có một số chức năng bổ sung được xây dựng để làm cho điều 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
 });

159
2018-04-04 02:19



Giải pháp jQuery hoạt động khá tốt đối với tôi! - larrydahooster
@ kba - Tôi sẽ không gọi chính xác câu trả lời này 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 nhiều cách để bao gồm các lời hứa gốc. - jfriend00
những ngày này 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 nó với FetchAPI và Promises - philx_x
@philx_x - Bạn đang làm gì về hỗ trợ IE và Safari? - jfriend00
@ jfriend00 github thực hiện một 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


Nhận phòng từ năm 2015: Hiện tại chúng tôi có lời hứa gốc trong trình duyệt mới nhất (Cạnh 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 phải là Internet Explorer, Opera Mini và các phiên bản cũ hơn của Android).

Nếu chúng tôi muốn thực hiện 10 hành động không đồng bộ và nhận được thông báo khi họ đã hoàn tất, chúng tôi có thể sử dụng Promise.all, không có 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);
});

12
2017-11-19 20:29



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


Bạn có thể sử dụng jQuery Hoãn lại đối tượng cùng với khi nào phương pháp.

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

10
2018-04-04 02:19



Câu hỏi không được gắn thẻ cho jQuery điều này thường có nghĩa là OP không muốn có câu trả lời jQuery. - jfriend00
@ jfriend00 Tôi không muốn phát minh lại bánh xe khi nó đã được tạo ra trong jQuery - Paul
@Paul thay vì vậy sau đó tái phát minh ra bánh xe của bạn bao gồm 40kb rác để làm một cái gì đó đơn giản (hoãn lại) - Raynos
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 chỉ ra rằng bằng cách bạn gắn thẻ câu hỏi của bạn với jQuery hay không. - jfriend00
Cuộc gọi $ .when là ví dụ này không chính xác. Để chờ một mảng trì hoãn / hứa hẹn bạn cần sử dụng $ .when.apply ($, promise) .then (function () {/ * do stuff * /}). - danw


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 cuộc gọi không đồng bộ sẽ thực hiện điều này:

countDownLatch.count++;

trong khi trong mỗi asynch gọi lại ở cuối của phương pháp 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 đếm ngược.


7
2018-04-04 02:21



Trong 99% tất cả các trường hợp sử dụng, Promise là một 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 trong đó một polyfill Promise lớn hơn thì JS sử dụng nó! - Sukima


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

Promise.all

FetchAPI

(đối với một số lý do Array.map không hoạt động bên trong .then chức năng cho tôi. Nhưng bạn có thể sử dụng một .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);
})

3
2018-04-14 19:30



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


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

1
2018-04-04 02:28