Несколько вызовов AJAX, 1 обратный вызов

Мне нужно получить данные (вопросы и ответы) из удаленной службы, используя их API. Данные разделены на разные категории, и единственные методы, которые они предлагают, позволяют мне перечислять категории и элементы из определенной категории. Однако Мой брифинг подразумевает, что я собираю данные из разных категорий. В итоге я сделал это (это соберет данные из всех категорий):

var getEveryQA = function(sLang)
{
    var allQA = [];
    //This request is for getting category listing
    $.ajax({
            crossDomain: true,
            contentType: "application/json; charset=utf-8",
            url: category_list_URL,
            data: { Lang: sLang }, 
            dataType: "jsonp",
            success: function(responseData){
                for (var i = 0; i < responseData.length; i++) 
                {
                    if(responseData[i].Code.toLowerCase !== "all")//category "all" has no real existence although it is returned in categories listing
                    {
                        //Request items for each category
                        $.ajax({
                            crossDomain: true,
                            contentType: "application/json; charset=utf-8",
                            url: items_by_category_URL,
                            data: { Lang: sLang, Category: responseData[i].Code }, 
                            dataType: "jsonp",
                            success: function(responseData){
                                    allQA = allQA.concat(responseData);//object from this response will be concatenated to the global object
                                }
                        });

                    }
                }
            }
        }); 
}

Я хотел бы запускать метод сортировки всякий раз, когда все вызовы AJAX, выполненные в моем цикле for, были успешными. У меня есть ощущение, что jQuery deferred является решением, но многие примеры, которые я прочитал, были несовместимы с моей структурой цикла for.... Есть ли способ создать какую-то «очередь» из моих нескольких обратных вызовов, которую я мог бы передать в качестве аргумента методу deferred? Или, может быть, я смотрю в неправильном направлении?


person Laurent S.    schedule 22.09.2014    source источник
comment
Используйте 1_. Поместите свои вызовы ajax в массив, затем вызовите $.when(thatarray).then(function(){Do something with all the results});. Вы можете добавить результаты в другой массив (по мере возврата в любом порядке) и отсортировать их в функции then.   -  person Gone Coding    schedule 22.09.2014
comment
@TrueBlueAussie имейте в виду, $.when не работает с массивом, если вы также не используете .apply   -  person Kevin B    schedule 22.09.2014
comment
@KevinB Попробуйте на console, эта страница: var q = []; q.push($.ajax(), $.ajax()); $.when(q).done(function(data) {console.log(data)}) // [Object, Object]. между прочим, альтернативно, var dfd = new $.Deferred(); dfd.resolveWith($, [$.ajax(), $.ajax()]); dfd.done(function(data) { console.log(data, data)})   -  person guest271314    schedule 22.09.2014
comment
@guest271314 guest271314 $.when, если дается не обещание, разрешается немедленно.   -  person Kevin B    schedule 22.09.2014
comment
@guest271314 Вот пример: jsfiddle.net/17a1uhms/2 Обратите внимание, как console.log 3 происходит немедленно а не через 2 секунды, и как console.log 4 происходит через 2 секунды из-за правильного использования $.when с .apply   -  person Kevin B    schedule 22.09.2014
comment
@KevinB At jsfiddle действительно регистрируется в порядке (не уверен, что это ожидаемый результат?); 1 (новая строка), 2 (новая строка), 3. да, рассмотрение не-promise объектов/значений и т. д., возможно, требуется немедленное разрешение ; хотя должен быть в состоянии привести объект к promise паре способов, включая dfd.promise(obj); obj.done(fn)   -  person guest271314    schedule 22.09.2014
comment
Да, он регистрируется по порядку, но на самом деле он не ждет завершения промисов. 1 2 и 3 происходят мгновенно, тогда как 3 должен ждать 2 секунды, как и 4. Если вы увеличили задержку 3 до 10 секунд, она все равно будет записываться до 4, которая ждет 2 секунды.   -  person Kevin B    schedule 22.09.2014
comment
Здесь: jsfiddle.net/17a1uhms/3 ожидаемый результат, основанный на вас и trueblueaussie, будет 1243, но на самом деле он регистрирует 1234.   -  person Kevin B    schedule 22.09.2014
comment
Здесь не было включено deferred фрагментов в Answer , так как оценка length задействованных массивов оказалась достаточной для удовлетворения требования . $.when() , .resolve() и .promise() должны принимать массив x без включения apply ; в частности, .resolveWith должен иметь такую ​​же функциональность, как $.when.apply. Опять же, обратите внимание на различия между объектами promise и объектами, отличными от promise, в отношении асинхронной обработки.   -  person guest271314    schedule 22.09.2014
comment
@KevinB Использование $.queue() для обработки объектов promise или не promise, возможно, обеспечивает большую универсальность и не требует приведения объекта, не являющегося обещанием, к объекту обещания. например, см. ссылку в ответе; в этой части использовал $.Deferred(), обернутый в $(), чтобы обеспечить функциональность $.queue(), затем, когда все задачи были выполнены, развернул $(), чтобы использовать $.Deferred() ниже. однако основным механизмом обработки был $.queue.   -  person guest271314    schedule 22.09.2014
comment
Я предпочитаю использовать нативный интерфейс promise или отдельный, чем jQuery, чтобы вы могли правильно обрабатывать ошибки через .catch. Но каждому свое. Проблема с очередью заключается в том, что она отправляет только один запрос за раз, что потенциально увеличивает общее время, необходимое для получения всех данных.   -  person Kevin B    schedule 22.09.2014
comment
С нативным интерфейсом и большинством других вы получаете этот метод с именем all, который ДЕЙСТВИТЕЛЬНО принимает массив промисов. developer.mozilla.org/en-US/ документы/Интернет/JavaScript/Справочник/   -  person Kevin B    schedule 22.09.2014
comment
Да. Как собственные обещания, так и обещания jquery могут быть дополнительно изменены или скорректированы в соответствии с требованиями автора. Можно отправлять несколько запросов/функций/задач через $.queue в одну функцию - если требуется или желательно. Можно также прикрепить .catch к концу обещания/отложенного jquery. вот фрагмент, добавляющий when и done к собственным обещаниям stackoverflow.com/a/23587868/2801559 . Спасибо !   -  person guest271314    schedule 22.09.2014
comment
На самом деле недавно пробовал all, похоже, возвращал разные результаты, если в цепочке массивов была ошибка? Если ошибки нет, результаты обрабатываются через обратный вызов, если единственная ошибка в цепочке, вызываются только обратные вызовы ошибок? Опять же, любой из них может быть скорректирован / изменен в соответствии с требованиями авторов.   -  person guest271314    schedule 22.09.2014
comment
Да, вот как это работает. Если произойдет какая-либо ошибка, вы не попадете в callbacks done, а сразу перейдете к catch.   -  person Kevin B    schedule 22.09.2014
comment
@KevinB Составленная часть, связанная в сообщении, чтобы избежать этой модели. Вместо этого обрабатываются все функции request , а затем , когда разрешена функция requests , можно отфильтровать возвращаемые значения успеха или ошибки ; делать вещи с коллекцией любых возвращаемых значений в requests.done . Например, если изменить один из url в объекте, содержащем их, на /echo/jsons/, этот error не остановит цепочку. Затем можно фильтровать коллекцию в requests.done и делать что-то с данными error или success. В качестве альтернативы можно также сохранить эту модель, присоединив функцию fail к функции request.   -  person guest271314    schedule 22.09.2014
comment
Право, все зависит от того, что вы хотите сделать. Существуют сотни различных способов решения этой проблемы.   -  person Kevin B    schedule 22.09.2014
comment
Спасибо за вашу помощь. Я вижу, что это не такая простая проблема :-)   -  person Laurent S.    schedule 23.09.2014


Ответы (2)


Из комментария: Используйте $.when. Поместите свои вызовы ajax в массив, затем вызовите $.when.apply(thatarray).then(function(){Do something with all the results});

Вы уже добавляете все свои результаты в другой массив (поскольку они возвращаются в любом порядке) и сортируете их и т. д. в функции then:

var getEveryQA = function(sLang)
{
    var allQA = [];
    //This request is for getting category listing
    $.ajax({
            crossDomain: true,
            contentType: "application/json; charset=utf-8",
            url: category_list_URL,
            data: { Lang: sLang }, 
            dataType: "jsonp",
            success: function(responseData){
                var requests = [];
                for (var i = 0; i < responseData.length; i++) 
                {
                    if(responseData[i].Code.toLowerCase !== "all")//category "all" has no real existence although it is returned in categories listing
                    {
                        //Request items for each category
                        requests.push($.ajax({
                            crossDomain: true,
                            contentType: "application/json; charset=utf-8",
                            url: items_by_category_URL,
                            data: { Lang: sLang, Category: responseData[i].Code }, 
                            dataType: "jsonp",
                            success: function(responseData){
                                    allQA = allQA.concat(responseData);//object from this response will be concatenated to the global object
                                }
                        }));
                    }
                }
                $.when.apply(requests).then(function(){
                    // Do something with your responses in allQA
                });
            }
        }); 
}

возвращаемое значение из $.ajax — это промис, который можно агрегировать с другими промисами. $.when — это почти единственный метод, который может принимать массив обещаний и позволять вам выполнять действия после завершения всех (или любых сбоев).

person Gone Coding    schedule 22.09.2014
comment
Я предпочитаю решение .when, но... вы не использовали его должным образом. $.when не принимает массив промисов, если только вы не используете .apply для применения массива в качестве нескольких аргументов к .when. - person Kevin B; 22.09.2014
comment
Техническая коррекция. $.when() не принимает массив. Он принимает несколько аргументов, которые являются обещаниями (на мой взгляд, огромная оплошность в дизайне, но так оно и есть). Вам нужно будет использовать $.when.apply($, array), чтобы передать массив. - person jfriend00; 23.09.2014
comment
Это решение в сочетании с использованием apply(), предложенное jfriend00, кажется, помогает. Я был близок к этому, но все же это заставило меня сэкономить довольно много времени! Я приму это решение, как только оно будет использовать метод apply() в последней инструкции. - person Laurent S.; 23.09.2014
comment
Обновлено. Я должен был запомнить это, но не использую это так часто. Ваше здоровье. - person Gone Coding; 23.09.2014
comment
Подтверждено! Но, пожалуйста, обновите также начало вашего ответа, чтобы он был совершенно правильным :-) спасибо за вашу помощь и знания. - person Laurent S.; 23.09.2014
comment
@Bartdude: Дох! Хорошо подмечено. Тоже исправлено :) - person Gone Coding; 24.09.2014

Редактировать

Это был бы хороший (и более простой) подход, но я не думаю, что он сработает. allQA будет состоять из всех QA всех категорий, и поэтому сортировка будет происходить после первой итерации (когда все, что мы поместили в этот массив, являются первыми результатами), в то время как мне нужно, чтобы это произошло, когда allQA «полный» — Бартчувак

Обратите внимание, что переменная ответа вызова $.ajax() в цикле for: _responseData (см. начальное подчеркивание _) — переменная, отличная от исходного $.ajax() ответа responseData (без начального подчеркивания).

Сортировка или другие задачи должны выполняться только один раз allQA.length === responseData.length -- не _responseData.length (то есть отдельные $.ajax() ответов, объединенные в цикле for).

Добавление , в том числе дополнительных deferred или promise элементов, не необходимых для выполнения задачи, описанной в исходном вопросе, если только ответ не требует решения с использованием методов deferred или promise.

length свойств задействованных объектов или массивов должно быть достаточно для выполнения задачи, описанной в OP.

Пример фрагмента на jsfiddle http://jsfiddle.net/guest271314/sccf49vr/ с использованием того же шаблона, что и изначально опубликовано:

var arr = [7, 5, 6, 1, 8, 4, 9, 0, 2, 3]; // unsorted array
$("body").append(arr + "<br />");
var getEveryQA = function (sLang) {
    var allQA = [];
    //This request is for getting category listing
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "/echo/json/",
        data: {
            json: JSON.stringify(sLang)
        },
        dataType: "json",
        success: function (responseData) {
            for (var i = 0; i < responseData.length; i++) {
                // category "all" has no real existence although 
                // it is returned in categories listing
                if (responseData[i] !== "all") {
                    //Request items for each category
                    $.ajax({
                        type: "POST",
                        contentType: "application/json; charset=utf-8",
                        url: "/echo/json/",
                        data: {
                            json: JSON.stringify(responseData[i])
                        },
                        dataType: "json",
                        // note leading `_` underscore at `for` loop
                        // `_responseData` -- different than initial 
                        // `responseData` response from first request
                        success: function (_responseData) {
                            //object from this response will be concatenated to the global object
                            allQA = allQA.concat(_responseData);
                            // do stuff when `allQA` length === `responseData` (for loop) length
                            console.log(allQA.length === responseData.length);
                            if (allQA.length === responseData.length) {
                                // do sorting stuff
                               allQA.sort(); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
                               console.log(allQA, arr);
                               $("body").append(allQA.join(",")); 
                            }
                        }
                    });
                }
            }
        }
    });
};

getEveryQA(arr);

Пытаться

for (var i = 0; i < responseData.length; i++)  {
  // category "all" has no real existence although 
  // it is returned in categories listing
  if(responseData[i].Code.toLowerCase !== "all") {
      //Request items for each category
      $.ajax({
        crossDomain: true,
        contentType: "application/json; charset=utf-8",
        url: items_by_category_URL,
        data: { Lang: sLang, Category: responseData[i].Code }, 
        dataType: "jsonp",
        success: function(_responseData){
          //object from this response will be concatenated to the global object
          allQA = allQA.concat(_responseData);
            // do stuff when `allQA` length === `responseData` (for loop) length
            if (allQA.length === responseData.length) {
              // do sorting stuff
            }
         }
     });    
   }
};
person guest271314    schedule 22.09.2014
comment
Это был бы хороший (и более простой) подход, но я не думаю, что он сработает. allQA будет состоять из всех QA всех категорий, и поэтому сортировка будет происходить после первой итерации (когда все, что мы вставили в этот массив, - это первые результаты), а мне нужно, чтобы это произошло, когда allQA заполнен - person Laurent S.; 23.09.2014
comment
@Bartdude Обратите внимание, что _ подчеркивание в начале $.ajax() вызывает ответ в for цикле. Включает начальное подчеркивание _, отличая эти переменные от исходного объекта responseData (без начального подчеркивания _). Смотрите обновленный пост, jsfiddle. Спасибо - person guest271314; 23.09.2014
comment
Конечно ! Виноват ! Тогда это голосование за, поскольку мы не можем проверить 2 ответа, а ответ от TrueBlueAussie привел меня к изучению функций «when()» и «apply()» и выглядит как более элегантный шаблон, хотя и более сложный для чтения, откуда Вижу. - person Laurent S.; 23.09.2014