Подтвердить что ты не робот

Javascript - синхронизация после асинхронных вызовов

У меня есть объект Javascript, для которого требуется 2 вызова внешнего сервера для создания его содержимого и делать что-либо значимое. Объект построен таким образом, что экземпляр экземпляра его автоматически будет выполнять эти 2 вызова. В двух вызовах используется общая функция обратного вызова, которая работает с возвращенными данными, а затем вызывает другой метод. Проблема в том, что следующий метод не следует вызывать до тех пор, пока оба метода не вернутся. Вот код, который я реализовал в настоящее время:

foo.bar.Object = function() {
this.currentCallbacks = 0;
this.expectedCallbacks = 2;

this.function1 = function() {
    // do stuff
    var me = this;
    foo.bar.sendRequest(new RequestObject, function(resp) {
        me.commonCallback(resp);
        });
};

this.function2 = function() {
    // do stuff
    var me = this;
    foo.bar.sendRequest(new RequestObject, function(resp) {
        me.commonCallback(resp);
        });
};

this.commonCallback = function(resp) {
    this.currentCallbacks++;
    // do stuff
    if (this.currentCallbacks == this.expectedCallbacks) {
        // call new method
    }
};

this.function1();
this.function2();
}

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

Спасибо за любую помощь.

4b9b3361

Ответ 1

Существует только другой способ, чем иметь этот счетчик. Другой вариант - использовать объект {} и добавить ключ для каждого запроса и удалить его, если закончите. Таким образом, вы сразу узнаете, что вернулось. Но решение остается прежним.

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

foo.bar.sendRequest(new RequestObject, function(resp) {
            me.commonCallback(resp);
            });

вы могли бы сделать это таким образом

foo.bar.sendRequest(new RequestObject, function(resp) {
            --me.expectedCallbacks || me.otherFunction(resp);
            });

Ответ 2

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

Вот функция, которая может вам помочь:

function gate(fn, number_of_calls_before_opening) {
    return function() {
        arguments.callee._call_count = (arguments.callee._call_count || 0) + 1;
        if (arguments.callee._call_count >= number_of_calls_before_opening)
            fn.apply(null, arguments);
    };
}

Эта функция известна как функция более высокого порядка - функция, которая принимает функции в качестве аргументов. Эта конкретная функция возвращает функцию, которая вызывает переданную функцию, когда она была вызвана number_of_calls_before_opening раз. Например:

var f = gate(function(arg) { alert(arg); }, 2);
f('hello');
f('world'); // An alert will popup for this call.

Вы можете использовать это как свой метод обратного вызова:

foo.bar = function() {
    var callback = gate(this.method, 2);
    sendAjax(new Request(), callback);
    sendAjax(new Request(), callback);
}

Второй обратный вызов, в зависимости от того, что будет, будет гарантировать, что вызывается method. Но это приводит к другой проблеме: функция gate вызывает переданную функцию без какого-либо контекста, то есть this будет ссылаться на глобальный объект, а не на объект, который вы строите. Существует несколько способов обойти это: вы можете закрыть this, наложив его на me или self. Или вы можете создать еще одну функцию более высокого порядка, которая делает именно это.

Здесь будет выглядеть первый случай:

foo.bar = function() {
    var me = this;        
    var callback = gate(function(a,b,c) { me.method(a,b,c); }, 2);

    sendAjax(new Request(), callback);
    sendAjax(new Request(), callback);
}

В последнем случае другая функция более высокого порядка будет выглядеть примерно так:

function bind_context(context, fn) {
    return function() {
        return fn.apply(context, arguments);
    };
}

Эта функция возвращает функцию, которая вызывает переданную функцию в переданном контексте. Примером этого может быть следующее:

var obj = {};
var func = function(name) { this.name = name; };
var method = bind_context(obj, func);
method('Your Name!');
alert(obj.name); // Your Name!

Чтобы выразить это, ваш код будет выглядеть следующим образом:

foo.bar = function() {
    var callback = gate(bind_context(this, this.method), 2);

    sendAjax(new Request(), callback);
    sendAjax(new Request(), callback);
}

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

Ответ 3

Я могу добавить, что У Underscore.js есть хороший помощник для этого:

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

_.after(count, function)

Код _after (как версия версии 1.5.0):

_.after = function(times, func) {
  return function() {
    if (--times < 1) {
      return func.apply(this, arguments);
    }
  };
};

Информация о лицензии (в версии 1.5.0)

Ответ 4

Что-то хорошее, мистер Кайл.

Чтобы сделать это немного проще, я обычно использую функцию "Старт" и "Готово".
- Функция Старт принимает список функций, которые будут выполнены.
Функция Готово вызывается обратными вызовами ваших функций, которые вы передали методу start.
Кроме того, вы можете передать функцию или список функций в готовый метод, который будет выполняться при завершении последнего обратного вызова.

Декларации выглядят следующим образом.

var PendingRequests = 0;
function Start(Requests) {
    PendingRequests = Requests.length;
    for (var i = 0; i < Requests.length; i++)
        Requests[i]();
};
//Called when async responses complete. 
function Done(CompletedEvents) {
PendingRequests--;
    if (PendingRequests == 0) {
        for (var i = 0; i < CompletedEvents.length; i++)
            CompletedEvents[i]();
    }
}

Вот простой пример использования google maps api.

//Variables
var originAddress = "*Some address/zip code here*"; //Location A
var formattedAddress; //Formatted address of Location B
var distance; //Distance between A and B
var location; //Location B

//This is the start function above. Passing an array of two functions defined below.
Start(new Array(GetPlaceDetails, GetDistances));


//This function makes a request to get detailed information on a place. 
//Then callsback with the **GetPlaceDetailsComplete** function
function GetPlaceDetails() {
    var request = {
        reference: location.reference //Google maps reference id
    };
    var PlacesService = new google.maps.places.PlacesService(Map);
    PlacesService.getDetails(request, GetPlaceDetailsComplete);
}

function GetPlaceDetailsComplete(place, status) {
    if (status == google.maps.places.PlacesServiceStatus.OK) {
        formattedAddress = place.formatted_address;
        Done(new Array(PrintDetails));
    }
}


function GetDistances() {
    distService = new google.maps.DistanceMatrixService();
    distService.getDistanceMatrix(
    {
        origins: originAddress, 
        destinations: [location.geometry.location], //Location contains lat and lng
        travelMode: google.maps.TravelMode.DRIVING,
        unitSystem: google.maps.UnitSystem.IMPERIAL,
        avoidHighways: false,
        avoidTolls: false
    }, GetDistancesComplete);
}
function GetDistancesComplete(results, status) {
    if (status == google.maps.DistanceMatrixStatus.OK) {
        distance = results[0].distance.text;
        Done(new Array(PrintDetails));
    }
}

function PrintDetails() {
    alert(*Whatever you feel like printing.*);
}

Итак, вкратце, то, что мы делаем здесь, - это -Пассирование массива функций функции Начать
Функция Старт вызывает функции в массиве и устанавливает количество PendingRequests
-В обратных вызовах для наших ожидающих запросов мы вызываем функцию Готово Функция Готово выполняет набор функций
- Функция Готово уменьшает счетчик PendingRequests
-Если их не более ожидающие запросы, мы вызываем функции, переданные функции Готово

Это простой, но практичный пример синхронизации веб-вызовов. Я попытался использовать пример того, что широко используется, поэтому я пошел с Google maps api. Я надеюсь, что кто-то найдет это полезным.

Ответ 5

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

Здесь функция execute_jobs - это точка входа. он принимает список данных для одновременного выполнения. Сначала он задает количество заданий для ожидания размера list. Затем он устанавливает таймер для проверки конечного условия (число падает до 0). И, наконец, он отправляет задание для каждой информации. Каждое задание уменьшает количество ожидаемых заданий на один.

Это будет выглядеть примерно так:

var g_numJobs = 0;

function async_task(data) {
    //
    // ... execute the task on the data ...
    //

    // Decrease the number of jobs left to execute.
    --g_numJobs;
}

function execute_jobs(list) {
    // Set the number of jobs we want to wait for.
    g_numJobs = list.length;

    // Set the timer (test every 50ms).
    var timer = setInterval(function() {
        if(g_numJobs == 0) {
            clearInterval(timer);
            do_next_action();
        }
    }, 50);

    // Send the jobs.
    for(var i = 0; i < list.length; ++i) {
        async_task(list[i]));
    }
}

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

Ответ 6

Я разделял одно и то же разочарование. Когда я подключил больше асинхронных вызовов, он стал аддоном обратного вызова. Итак, я придумал свое решение. Я уверен, что есть похожие решения, но я хотел создать что-то очень простое и удобное в использовании. Asynq - это script, который я написал для асинхронных задач цепочки. Итак, чтобы запустить f2 после f1, вы можете сделать:

asynq.run(f1, f2)

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