2012-04-04 17 views
63

tôi có mã trông giống như thế này trong javascript:Làm cách nào để đợi tập hợp các chức năng gọi lại không đồng bộ?

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

Sau TẤT CẢ của những cuộc gọi async đượ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.

Tôi làm cách nào để chờ tất cả chúng?

ý tưởng duy nhất của tôi bây giờ là có một mảng các phép toán được gọi là làm, và thiết lập được thực hiện [i] là true trong hàm thứ i gọi lại, sau đó nói trong khi (không phải tất cả đều đượ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.

Xin cảm ơn trước.

+1

Khi bạn đồng ý chờ yêu cầu Ajax hoàn tất? –

+5

Lưu ý, 'trong khi (không phải tất cả được thực hiện) {}' 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

+0

Có. 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

Trả lời

137

Bạn chưa thật cụ thể với 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 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 các dữ liệu trong một mảng và theo dõi khi người cuối cùng đã hoàn tất:

Counter Manual

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 là quan trọng ở đây (không được hiển thị vì cụ thể là 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, những lời hứa thường được sử dụng để giải quyết vấn đề kiểu này kể từ jQuery $.ajax() đã trả về 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 đều giải quyết và sẽ thu thập các kết quả trả lại 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 Chuẩn Promises

As specified in kba's answer: nếu bạn có một môi trường với những lời hứa mẹ đẻ built-in (trình duyệt hiện đại hay Node.js hoặc sử dụng babeljs transpile hoặc sử dụng một polyfill lời hứa), sau đó bạn có thể sử dụng những lời hứa ES6 chỉ định. Xem this table để được 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 phi hứa async thành một trong đó trả về một lời hứa, bạn có thể "promisify" nó như thế này:

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

Và, sau đó sử dụng các mô hình 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 
}); 

Bluebird Promises

Nếu bạn sử dụng một tính năng hơn thư viện phong phú như Bluebird promise library, sau đó nó có một số chức năng bổ sung xây dựng trong để làm đ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 
}); 
+1

Giải pháp jQuery hoạt động khá tốt đối với tôi! – larrydahooster

+3

@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 á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

+0

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 việc đó với FetchAPI và Promises –

9

Bạn có thể sử dụng đối tượng Deferred của jQuery cùng với phương pháp 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ẻ cho 'jQuery' thường có nghĩa là OP không muốn có 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, thay vì vậy hãy tái phát minh ra bánh xe của bạn bao gồm 40kb rác để làm điều gì đó đơn giản (hoãn) – Raynos

7

Bạn có thể bắt chước 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ộ d oes này:

countDownLatch.count++; 

trong khi trong mỗi cuộc gọi asynch trở lại vào cuối của phương pháp này bạn thêm dòng này:

countDownLatch.check(); 

Nói cách khác, bạn bắt chước một chức năng đếm xuống-chốt.

+0

Trong 99% của tất cả các trường hợp sử dụng một lời hứa là con đường để đ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

1

Sử dụng một thư viện kiểm soát dòng chảy 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) 
}) 
9

Kiểm tra từ năm 2015: Chúng tôi bây giờ có native promises trong most recent browser (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 phải là 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 async và nhận được thông báo khi họ đều đã hoàn thành, chúng ta có thể sử dụng bản địa Promise.all, mà không cần bất kỳ thư viện bên ngoài:

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

'Promises.all()' nên là 'Promise.all()'. – jfriend00

+1

Câu trả lời của bạn cũng cần tham khảo [trình duyệt nào] (http://caniuse.com/#feat=promises) bạn có thể sử dụng 'Promise.all()' trong đó bao gồm không có phiên bản hiện tại của IE. – jfriend00

2

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

Promise.all

FetchAPI

(vì một lý do Array.map không hoạt động bên trong chức năng .Sau đó cho tôi. Nhưng bạn có thể sử dụng một và [] .concat .forEach() 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); 
}) 
+0

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

Các vấn đề liên quan