Я хочу получить два ресурса с помощью двух асинхронных вызовов. Я хочу продолжить двигаться только, когда оба ресурса были получены.
Как я могу сделать это изящно в JS?
Это работало бы:
getStuff1(function (result1) {
getStuff2 (function (result2) {
// do stuff with result1 and result2
....
}
}
но stuff2 только запускается после того, как stuff1 завершается. Я предпочел бы запускать stuff2 при ожидании на stuff1.
Если вы знаете, что функции на самом деле являются объектами первого класса в Javascript, вы можете придумать довольно элегантное решение.
Без каких-либо дополнительных объектов, или глобальных переменных.
function callback1() {
callback1.done = true;
commonCallback();
}
function callback2() {
callback2.done = true;
commonCallback();
}
function commonCallback() {
if (callback1.done && callback2.done) {
// do stuff, since you know both calls have come back.
}
}
Почему это так элегантно? Потому что вы инкапсулировали данные, ваша область видимости свободна от бесполезных переменных, а код более читабелен, чем когда-либо. Как круто :)
UPDATE
А если вам нужно более общее решение, вы можете попробовать следующее:
function callback() {
callback.number -= 1;
if (callback.number === 0) {
// do stuff since all calls finished
callback.last();
}
}
callback.newQueue = function(num, last) {
callback.number = num;
callback.last = last;
}
// EXAMPLE USAGE
// our last function to be invoked
function afterEverythingDone(){ alert("done"); }
// create a new callback queue
callback.newQueue(3, afterEverythingDone);
// as time passes you call the callback
// function after every request
callback();
callback();
callback();
// after all call is finished
// afterEverythingDone() executes
Снова крутость :)
Один из способов - использовать один и тот же обратный вызов для обоих запросов и продолжить, когда оба будут выполнены:
var requestStatus = {
fooComplete: false,
barComplete: false
};
function callback(data) {
if (isFoo(data)) {
requestStatus.fooComplete = true;
} else if (isBar(data)) {
requestStatus.barComplete = true;
}
if (requestStatus.fooComplete && requestStatus.barComplete) {
proceed();
}
}
getAsync("foo", callback);
getAsync("bar", callback);
Вы, вероятно, захотите реализовать это в классе.
Edit : добавлены асинхронные вызовы для ясности
У вас может быть функция обратного вызова для каждого из них, указывающая, что их соответствующий запрос вернулся, а затем выполнение той же общей функции. Для иллюстрации:
var call1isBack = false;
var call2isBack = false;
function call1Callback() {
call1isBack = true;
commonCallback();
}
function call2Callback() {
call2isBack = true;
commonCallback();
}
function commonCallback() {
if (call1isBack && call2isBack) {
// do stuff, since you know both calls have come back.
}
}
Вот фрагмент из параллельной библиотеки, над которой я работаю. Все, что вам нужно сделать, это создать экземпляр нового Concurrent.Counter с количеством ожидающих запросов (перед их выполнением) и обратный вызов для выполнения, когда они будут выполнены. Перед возвратом каждой из асинхронных функций попросите ее вызвать метод декремента () счетчика; как только счетчик будет уменьшен указанное количество раз, обратный вызов будет выполнен:
// Ensure the existence of the "Concurrent" namespace
var Concurrent = Concurrent || {};
/**
* Constructs a new Concurrent.Counter which executes a callback once a given number of threads have
* returned. Each Concurrent.Counter instance is designed to be used only once, and then disposed of,
* so a new one should be instantiated each additional time one is needed.
*
* @param {function} callback The callback to execute once all the threads have returned
* @param {number} count The number of threads to await termination before executing the callback
*/
Concurrent.Counter = function(callback, count) {
/**
* Decrements the thread count, and executes the callback once the count reaches zero.
*/
this.decrement = function() {
if (!(-- count)) {
callback();
}
};
};
// The number of asynchronous requests to execute
var requests = 10,
// Executes a callback once all the request tasks have returned
counter = new Concurrent.Counter(function() {
// this will be executed once the tasks have completed
}, requests),
// Tracks the number of requests made
i;
for (i = 0; i < requests; i ++) {
setTimeout(function() {
/*
* Perform an asynchronous task
*/
// Decrement the counter
counter.decrement();
}, 0);
}
Это списано у меня в голове, но должно работать.
function createCoordinator(onFinish) {
var count = 0;
return function (callback) {
count++;
return function () {
if (callback.apply(this, arguments))
count--;
if (count == 0)
onFinish();
}
}
}
var coordinate = createCoordinator(function () { alert('done!') });
// Assume sendAJAX = function (url, onreadystatechange)
sendAJAX('url1', coordinate(function () {
if (this.readyState != 4)
return false; // Return false if not done
alert('Done with url1!');
return true;
}));
sendAJAX('url2', coordinate(function () {
if (this.readyState != 4)
return false; // Return false if not done
alert('Done with url2!');
return true;
}));
Используйте общий обработчик обратного вызова со счетчиком, который позволяет переход в «фактическую» секцию обработки только после того, как счетчик достигнет или превысит количество ожидающих запросов:
var commonHandler = (function() {
var counter=0, pendingCalls=2;
return function() {
if (++counter >= pendingCalls) {
// Do the actual thing...
}
}
})();
makeAjaxCall({args:args1, onComplete:commonHandler});
makeAjaxCall({args:args2, onComplete:commonHandler});
Использование закрытия вокруг анонимного функция позволяет избежать использования глобальной переменной для счетчика.