Координирование асинхронных запросов в JavaScript

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

Как я могу сделать это изящно в JS?

Это работало бы:

getStuff1(function (result1) {
    getStuff2 (function (result2) {
        // do stuff with result1 and result2
        ....
    }
}

но stuff2 только запускается после того, как stuff1 завершается. Я предпочел бы запускать stuff2 при ожидании на stuff1.

14
задан Gilbert 9 July 2010 в 22:05
поделиться

6 ответов

Если вы знаете, что функции на самом деле являются объектами первого класса в 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

Снова крутость :)

18
ответ дан 1 December 2019 в 11:59
поделиться

Один из способов - использовать один и тот же обратный вызов для обоих запросов и продолжить, когда оба будут выполнены:

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 : добавлены асинхронные вызовы для ясности

3
ответ дан 1 December 2019 в 11:59
поделиться

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

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.
  }
}
3
ответ дан 1 December 2019 в 11:59
поделиться

Вот фрагмент из параллельной библиотеки, над которой я работаю. Все, что вам нужно сделать, это создать экземпляр нового 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);
}
0
ответ дан 1 December 2019 в 11:59
поделиться

Это списано у меня в голове, но должно работать.

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;
}));
0
ответ дан 1 December 2019 в 11:59
поделиться

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

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

Использование закрытия вокруг анонимного функция позволяет избежать использования глобальной переменной для счетчика.

1
ответ дан 1 December 2019 в 11:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: