Несколько вложенных вызовов AJAX в jQuery: как сделать это правильно, не возвращаясь к синхронности?

#ajax #facebook-graph-api #asynchronous #synchronous #ajax-request

#ajax #facebook-graph-api #асинхронный #синхронный #ajax-запрос

Вопрос:

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

Функция

 $.fn.facebookEvents = function(options){
    var fbEvents = 'https://graph.facebook.com/' options.id '/events/?access_token=' options.access_token 'amp;since=nowamp;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 Ku00f6ln",
  "name": "Ku00f6rner // Gu00e4nsehaut Tour 2017 // Ku00f6ln",
  "start_time": "2017-03-16T20:00:00 0100",
  "timezone": "Europe/Berlin",
  "id": "985939951529300" 
},
  

К сожалению, в них отсутствуют необходимые нам детали. Они скрыты (вложены) и могут быть найдены после создания второго getJSON (строка 8 в функции), используя индивидуальный «идентификатор» каждого объекта. Теперь Facebook отвечает:

 {
   "description": "http://www.eventim.de/koernernTickets 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 Ku00f6ln",
   "name": "Ku00f6rner // Gu00e4nsehaut Tour 2017 // Ku00f6ln",
   "owner": {
      "name": "Ku00f6rner",
      "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 Ku00f6ln"
   },
   "id": "985939951529300"
}
  

И вуаля, детали, которые я искал. После выполнения этого второго (вложенного) AJAX-вызова я помещаю данные JSON в массив «события» (строка 12).

Проблема

Это должно заполнять массив с каждой итерацией .each. Однако взгляните на два «console.logs». Первый возвращает заполненный массив, второй возвращает пустой массив… Однако, если вызовы AJAX выполняются синхронно (что не так, как должно быть), например, путем добавления $.ajaxSetup({async: false}); перед функцией, это работает нормально. Массив заполнен и может быть отображен (строка 18).

Вопрос

Как можно объяснить такое поведение и как можно правильно выполнить эту функцию? Я знаю, должен быть способ использовать отложенные и обещания? Я думал, что сделал, используя .when.then.done … очевидно, я ошибся.

С наилучшими пожеланиями, Джулиус

Ответ №1:

Очевидно, вы ничего не знаете об обещаниях. Я думаю, я бы сделал это другим способом, но, используя promises, вы могли бы сделать следующее:

 $.fn.facebookEvents = function(options){
    var fbEvents = 'https://graph.facebook.com/' options.id '/events/?access_token=' options.access_token 'amp;since=nowamp;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(doneamp;amp;completed) {
                  deferred.resolve(events);
                }
            }));
        });
        done=true;
    });
};
  

Ответ №2:

спасибо за ваш ответ. Для полноты картины я придумал альтернативное решение.

 $.fn.facebookEvents = function(options){
    var fbEvents = 'https://graph.facebook.com/' options.id '/events/?access_token=' options.access_token 'amp;since=nowamp;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
    };

};