2009-05-13 2 views
20

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

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 в течение нескольких недель, и мне интересно, есть ли лучший способ сделать то же самое, что я еще должен наткнуться.

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

+0

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

ответ

5

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

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

Если вы не хотите сериализовать 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); 
} 

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

8

Я могу добавить, что Underscore.js has a nice little helper for this:

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

_.after(count, function) 

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

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

The license info (а-версии 1.5.0)

+1

_.after (count, _.bind (fun, this);) – tribalvibes

2

Вот некоторые хорошие вещи Mr. Кайл.

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

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

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

Таким образом, в двух словах, что мы делаем здесь
-Passing массив функций в функции Start
-The Start функция вызывает функции в массиве и устанавливает количество PendingRequests
-В обратных вызовов для наших ожидающих запросов, мы называем функцию Готовы -The Совершенно фу nction принимает массив функций
-The Совершено функция вычитает счетчик PendingRequests
-Если их не более запросов, ожидающих рассмотрения, мы называем функции, переданные функции Done

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

0

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

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

-1

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

asynq.run (f1, f2)

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

Смежные вопросы