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

У меня есть объект Javascript, который требует 2 вызовов на внешний сервер для создания его содержимого и выполнения чего-либо значимого. Объект построен таким образом, что экземпляр экземпляра будет автоматически выполнять эти 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 всего несколько недель, и мне интересно, есть ли лучший способ сделать то же самое, на что я еще не наткнулся.

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

6 ответов


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

вы можете немного изменить код. Если это похоже на ваш пример, вам нужно только вызвать другую функцию внутри 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);
            });

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

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

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


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

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

_.after(count, function)

код _after (as-версии 1.5.0):

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

лицензия info (как-в версии 1.5.0)


это хороший материал, Мистер Кайл.

проще говоря, я обычно использую функцию Start и Done.
- The старт функция принимает список функций, которые будут выполняться.
- The сделал функция вызывается обратными вызовами ваших функций, которые вы передали методу start.
- Кроме того, вы можете передать функцию или список функций методу done, который будет выполнен после завершения последнего обратного вызова.

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

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

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

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


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

функции 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 классы. The Job выполнит обратный вызов и уменьшит количество ожидающих заданий, в то время как JobList будет агрегировать таймер и вызвать обратный вызов следующего действия, как только задания будут завершены.


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

asynq.run (f1, f2)

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