Несколько вложенных AJAX-вызовов в jQuery: как это сделать правильно, не возвращаясь к синхронному режиму?
Я знаю, что здесь уже есть много вопросов, касающихся AJAX и Synchronicity, но я почему-то не нашел правильного ответа на мою причину. Пожалуйста, будьте нежны, я нуб, так что это может быть дубликатом. Тем не менее, даже намеки на это могут помочь мне и другим найти правильный ответ.
Функция
$.fn.facebookEvents = function(options){
var fbEvents = 'https://graph.facebook.com/'+options.id+'/events/?access_token='+options.access_token+'&since=now&limit=500';
var events = [];
$.when($.getJSON(fbEvents)).then(function(json){
$.each(json.data, function(){
$.getJSON('https://graph.facebook.com/'+this.id+'/?access_token='+options.access_token, function(jsonData){
events.push(jsonData);
console.log(events); // here array "events" is filled successively
});
console.log(events); // here array "events" remains empty
});
}).done(function(){
$("#fb_data_live").html(
$("#fb_events").render(events)
);
});
};
В этой функции происходит вызов AJAX (через сокращение jQuerys getJSON) для списка событий на странице Facebook. Это вернет список объектов JSON, представляющих каждую дату. Пример одного объекта в качестве ответа:
{
"end_time": "2017-03-16T23:00:00+0100",
"location": "Yuca K\u00f6ln",
"name": "K\u00f6rner // G\u00e4nsehaut Tour 2017 // K\u00f6ln",
"start_time": "2017-03-16T20:00:00+0100",
"timezone": "Europe/Berlin",
"id": "985939951529300"
},
К сожалению, в них отсутствуют детали, которые нам нужны. Они скрыты (вложены) и могут быть найдены после создания второго getJSON (строка 8 в функции), используя индивидуальный "id" каждого объекта. Теперь Facebook отвечает:
{
"description": "http://www.eventim.de/koerner\nTickets ab sofort exklusiv auf eventim.de und ab MI 07.09. \u00fcberall wo es Tickets gibt sowie auf contrapromotion.com.",
"end_time": "2017-03-16T23:00:00+0100",
"is_date_only": false,
"location": "Yuca K\u00f6ln",
"name": "K\u00f6rner // G\u00e4nsehaut Tour 2017 // K\u00f6ln",
"owner": {
"name": "K\u00f6rner",
"category": "Musician/Band",
"id": "366592010215263"
},
"privacy": "OPEN",
"start_time": "2017-03-16T20:00:00+0100",
"timezone": "Europe/Berlin",
"updated_time": "2016-09-28T10:31:47+0000",
"venue": {
"name": "Yuca K\u00f6ln"
},
"id": "985939951529300"
}
Et вуаля, детали, которые я искал. После этого второго (вложенного) вызова AJAX я помещаю данные JSON в массив "events" (строка 12).
Эта проблема
Это должно заполнять массив при каждой итерации.each. Однако взгляните на два "console.logs". Первый возвращает заполненный массив, второй возвращает пустой массив... Если вызовы AJAX выполняются синхронно (что не так, как предполагается), например, путем добавления $.ajaxSetup({async: false});
до функции, она работает нормально, однако. Массив заполнен и может быть отображен (строка 18).
Вопрос
Как объяснить это поведение и как правильно выполнить эту функцию? Я знаю, должен быть способ использовать отсрочку и обещания? Я думал, что сделал, используя.when.then.done... очевидно, я ошибся.
С наилучшими пожеланиями, Юлий
2 ответа
Очевидно, вы ничего не знаете об обещаниях. Думаю, я бы сделал это по-другому, но, используя обещания, вы могли бы сделать следующее:
$.fn.facebookEvents = function(options){
var fbEvents = 'https://graph.facebook.com/'+options.id+'/events/?access_token='+options.access_token+'&since=now&limit=500';
var events = [];
var deferred=$.Deferred();
var promises=[];
var done=false;
$.when(deferred).then(function(events) {
console.log(events);
});
$.getJSON(fbEvents).done(function(json){
$.each(json.data, function(){
promises.push($.getJSON('https://graph.facebook.com/'+this.id+'/?access_token='+options.access_token).done(function(jsonData){
events.push(jsonData);
var completed=promises.filter(function(element) { return element.state=='pending' }).length==0;
if(done&&completed) {
deferred.resolve(events);
}
}));
});
done=true;
});
};
Спасибо за Ваш ответ. Ради полноты я придумал альтернативное решение.
$.fn.facebookEvents = function(options){
var fbEvents = 'https://graph.facebook.com/'+options.id+'/events/?access_token='+options.access_token+'&since=now&limit=500';
var events = [];
var eventsData = [];
var i;
// first call
function A() {
return $.getJSON(fbEvents).then(function(json){
events.push(json);
i = events[0].data.length;
});
};
// second call for objects details
function B(resultFromA) {
$.each(events[0].data, function(){
$.getJSON('https://graph.facebook.com/'+this.id+'/?access_token='+options.access_token, function(jsonData){
eventsData.push(jsonData);
i--;
if ( i == 0 ){
output(eventsData);
};
});
});
};
A().then(B);
function output() {
//render here
};
};