Есть ли способ установить функцию обработчика, когда произошел набор событий? - PullRequest
2 голосов
/ 15 марта 2010

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

var r1 = new Ajax.Request(url1, ...);
var r2 = new Ajax.Request(url2, ...);

function on_both_requests_complete(resp1, resp2) {
   ...
}

Один из способов - использовать опрос, но я думаю, что должен быть лучший способ.

Обновление: приемлемое решение должно быть без условий гонки.

Ответы [ 5 ]

1 голос
/ 16 марта 2010

Вот как бы я это сделал. Этот подход является общим, который дает вам больше гибкости и повторного использования, а также избегает объединения и использования глобальных переменных.

var makeEventHandler = function(eventMinimum, callback) {
    var data = [];
    var eventCount = 0;
    var eventIndex = -1;

    return function() {
        // Create a local copy to avoid issues with closure in the inner-most function
        var ei = ++eventIndex;
        return function() {
            // Convert arguments into an array
            data[ei] = Array.prototype.slice.call(arguments);

            // If the minimum event count has not be reached, return
            if ( ++eventCount < eventMinimum  ) {
                return;
            }

            // The minimum event count has been reached, execute the original callback
            callback(data);
        };
    };
};

Общее использование:

// Make a multiple event handler that will wait for 3 events
var multipleEventHandler = makeMultipleEventHandler(3, function(data) {
    // This is the callback that gets called after the third event
    console.log(data);
});

multipleEventHandler()(1,2,3);
var t = multipleEventHandler();
setTimeout(function() {t("some string");}, 1000);
multipleEventHandler()({a: 4, b: 5, c: 6});

Вывод из обратного вызова (сжатый Firebug):

 [[1, 2, 3], ["some string"], [Object { a=4,  more...}]]

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

Чтобы использовать это в контексте ваших запросов Ajax:

var onBothComplete = makeMultipleEventHandler(2, function(data) {
    // Do something
    ...
});
new Ajax.Request(url1, {onComplete: onBothComplete()});
new Ajax.Request(url2, {onComplete: onBothComplete()});

Редактировать: я обновил функцию, чтобы заставить data всегда поддерживать асинхронно полученные данные о событиях в синхронно выполняемом порядке (предыдущее предупреждение больше не существует).

1 голос
/ 15 марта 2010

В функции обратного вызова каждого запроса установите логическое значение, например

request1Complete и request2Complete

и звоните on_both_requests_complete(resp1,resp2).

В функции обработчика проверьте, установлены ли оба логических значения. Если нет, просто вернитесь и выйдите из функции. Функции обратного вызова должны быть сериализованы, чтобы они не могли происходить одновременно, поэтому это должно работать. Если бы они могли происходить параллельно, вы бы нарушали условия гонки.

0 голосов
/ 16 марта 2010

На основе ответа Джастина Джонсона на этот вопрос :

function sync(delays /* Array of Functions */, on_complete /* Function */) {
    var complete_count = 0;
    var results = new Array(delays.length);

    delays.length.times(function (i) {
        function on_progress(result) {
            results[i] = result;
            if (++complete_count == delays.length) {
                on_complete(results);
            }
        }
        delays[i](on_progress);
    });
}

Это предполагает, что каждая задержка принимает один аргумент: обработчик события «в процессе», который принимает один аргумент: результат, который задержка пытается вычислить. Чтобы завершить пример в моем первоначальном вопросе, вы должны использовать его так:

var delays = [];
delays[0] = function (on_progress) {
    new Ajax.Request(url1, {onSuccess: on_progress});
};
delays[1] = function (on_progress) {
    new Ajax.Request(url2, {onSuccess: on_progress});
};
function on_complete(results) { alert(results.inspect()); }
sync(delays, on_complete);

Единственное, в чем я не уверен, так это в том, избегает ли это условий гонки. Если выражение ++ complete_count == delays.length всегда происходит атомарно, то это должно сработать.

0 голосов
/ 15 марта 2010

Вы можете использовать концепцию, в которой вы устанавливаете временные переменные и ждете «последнего» запроса. Для этого вы можете иметь две функции-дескриптора, которые устанавливают в tmp vars значение возврата val, а затем вызывать вашу функцию «on_both_requests_complete».

var processed = false;
var r1 = new Ajax.Request(...);
var r2 = new Ajax.Request(...);

(function() {
 var data1;
 var data2;

 function handle_r1(data) {
   data1 = data;
   on_both_requests_complete(); 
 };

 function handle_r2(data) {
   data2 = data;
   on_both_requests_complete();
 };

 function on_both_requests_complete() {
  if ( (!data1 || !data2) || processed) {
    return;
  }
  processed = true;

  /* do something */
 };
}();
0 голосов
/ 15 марта 2010

Хорошо, вы должны помнить, что реализация JS в браузерах на самом деле не параллельна, и используйте это в своих интересах. Итак, что вы хотели бы сделать, это проверить каждый обработчик, завершил ли другой. Пример в jQuery:

var other_done = false;
$.get('/one', function() {
  if (other_done) both_completed();
  other_done = true;
  alert('One!');
});
$.get('/two', function() {
  if (other_done) both_completed();
  other_done = true;
  alert('Two!');
});
function both_completed() {
  alert('Both!');
}
...