2012-04-04 7 views
63

У меня есть код, который выглядит, как это в JavaScript:Как подождать набора асинхронных функций обратного вызова?

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

После все этих асинхронных вызовов сделаны, я хочу, чтобы вычислить мин по всем массивам.

Как я могу ждать их всех?

Моя единственная идея прямо сейчас, чтобы иметь массив булевых называемыми сделано, и множество сделало [я] истину в функции Ith обратного вызова, то говорит, в то время как (не все сделано) {}

редактировать: Я предполагаю, что одно возможное, но уродливое решение будет состоять в том, чтобы отредактировать обработанный массив в каждом обратном вызове, а затем вызвать метод, если все остальные выполняются с каждого обратного вызова, таким образом, последний обратный вызов будет вызван продолжающимся методом.

Заранее спасибо.

+1

В случае асинхронного вызова вы ожидаете завершения запроса Ajax? –

+5

Обратите внимание: 'while (не все сделано) {}' не будет работать. Пока вы заняты, ни один из ваших обратных вызовов не может работать. – cHao

+0

Да. Я жду асинхронного вызова внешнего API, чтобы он возвращался, чтобы он вызывал методы обратного вызова.Да, caao, я понял это, поэтому я прошу о помощи здесь: D – codersarepeople

ответ

137

Вы не очень-то определились с вашим кодом, поэтому я состану сценарий. Допустим, у вас 10 вызовов ajax, и вы хотите аккумулировать результаты этих 10 аякс-звонков, а затем, когда они все закончили, вы хотите что-то сделать. Вы можете сделать это так, накапливая данные в массиве и отслеживании, когда последний закончил:

Ручной счетчик

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

Примечание: обработка ошибок важна здесь (не показано, так как это зависит от того, как вы делаете свои айакс-звонки). Вам нужно подумать о том, как вы собираетесь справляться с ситуацией, когда один вызов ajax никогда не завершается, либо с ошибкой, либо застрял в течение долгого времени или времени после долгого времени.


JQuery Обещания

Добавление к моему ответу в 2014 г. В эти дни, обещания часто используются для решения такого рода проблемы, так как JQuery-х $.ajax() уже возвращает обещание и $.when() даст вам знать, когда группа обещаний все решить и будет собирать результаты возврата для вас:

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 Обещание

As specified in kba's answer: если у вас есть среда с носителями обещаний встроенных (современным браузером или node.js или с помощью babeljs transpile или используя обещание polyfill), то вы можете использовать ES6-определенные обещания. См. this table для поддержки браузера. Обещания поддерживаются практически во всех текущих браузерах, кроме IE.

Если doAjax() возвращает обещание, то вы можете сделать это:

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

Если вам нужно сделать операцию, не обещание асинхронной в тот, который возвращает обещание, вы можете «promisify» его как это:

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

И затем использовать шаблон выше:

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 Обещания

Если вы используете более функциональную богатую библиотеку таких как Bluebird promise library, то она имеет некоторые дополнительные функции, встроенные в, чтобы сделать это проще:

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

Решение jQuery работает очень хорошо для меня! – larrydahooster

+3

@kba - Я бы точно не назвал этот ответ устаревшим, поскольку все методы все еще применимы, особенно если вы уже используете jQuery для Ajax. Но я обновил его несколькими способами, чтобы включить собственные обещания. – jfriend00

+0

В те времена существует намного более чистое решение, которое даже не требует jquery. Я делаю это с помощью FetchAPI и Promises –

9

Вы можете использовать объект jQuery Deferred вместе с методом 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

Вопрос не был помечен для 'jQuery', который обычно означает, что OP не хочет отвечать на jQuery. – jfriend00

+8

@ jfriend00 Я не хотел изобретать велосипед, когда он уже был создан в jQuery – Paul

+4

@Paul, а затем заново изобрести колесо, в которое вы включили 40kb мусора, чтобы сделать что-то простое (отложенное) – Raynos

7

Вы можете эмулировать это примерно так:

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

затем каждый асинхронный вызов d OES это:

countDownLatch.count++; 

в то время как в каждом асинхронная обратного вызова в конце способа добавить эту строку:

countDownLatch.check(); 

Другими словами, вы эмулировать кол-вниз-защелки функциональность.

+0

В 99% всех случаев использования Promise - это способ пойти но мне нравится этот ответ, потому что он иллюстрирует метод управления кодом Async в ситуациях, когда многозадачность Promise больше, чем JS, который его использует! – Sukima

1

Используйте библиотеку управления потоком, как 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

Проверка в с 2015 года: Теперь у нас native promises в most recent browser (Грань 12, Firefox 40, Chrome 43, Safari 8, Opera 32 и Android-браузер 4.4.4 и iOS Safari 8.4, но не Internet Explorer, Opera Mini и более старые версии Android).

Если мы хотим выполнить 10 асинхронное действия и получить уведомление, когда они уже все закончили, мы можем использовать родной Promise.all, без каких-либо внешних библиотек:

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()' должно быть 'Promise.all()'. – jfriend00

+1

В вашем ответе также должны быть указаны [какие браузеры] (http://caniuse.com/#feat=promises), вы можете использовать 'Promise.all()', в котором нет текущих версий IE. – jfriend00

2

Это самый изящный способ, на мой взгляд ,

Promise.all

FetchAPI

(по какой-то причине Array.map не работает внутри .then функций для меня. Но вы можете использовать и [] .concat .forEach() или что-то подобное)

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

Я думаю, что это должно быть 'return response.map (response => {return response.json();})' или 'return response.map (response => response.json())'. –

Смежные вопросы