Обратный вызов jQuery для нескольких вызовов ajax

132

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

$('#button').click(function() {
    fun1();
    fun2();
    fun3();
//now do something else when the requests have done their 'success' callbacks.
});

var fun1= (function() {
    $.ajax({/*code*/});
});
var fun2 = (function() {
    $.ajax({/*code*/});
});
var fun3 = (function() {
    $.ajax({/*code*/});
});
MisterIsaak
источник

Ответы:

112

Вот объект обратного вызова, который я написал, в котором вы можете либо установить один обратный вызов, который запускается после завершения всех операций, либо позволить каждому иметь свой собственный обратный вызов и запускать их все после завершения:

УВЕДОМЛЕНИЕ

Начиная с jQuery 1.5+, вы можете использовать отложенный метод, как описано в другом ответе:

  $.when($.ajax(), [...]).then(function(results){},[...]);

Пример отложенного здесь

для jQuery <1.5 будет работать следующее, или если вам нужно, чтобы ваши вызовы ajax запускались в неизвестное время, как показано здесь, с двумя кнопками: запускается после нажатия обеих кнопок

[использование]

для одного обратного вызова после завершения: Рабочий пример

// initialize here
var requestCallback = new MyRequestsCompleted({
    numRequest: 3,
    singleCallback: function(){
        alert( "I'm the callback");
    }
});

//usage in request
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});

у каждого из них будет свой обратный вызов, когда все будет выполнено: Рабочий пример

//initialize 
var requestCallback = new MyRequestsCompleted({
    numRequest: 3
});

//usage in request
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the first callback');
        });
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the second callback');
        });
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the third callback');
        });
    }
});

[Код]

var MyRequestsCompleted = (function() {
    var numRequestToComplete, requestsCompleted, callBacks, singleCallBack;

    return function(options) {
        if (!options) options = {};

        numRequestToComplete = options.numRequest || 0;
        requestsCompleted = options.requestsCompleted || 0;
        callBacks = [];
        var fireCallbacks = function() {
            alert("we're all complete");
            for (var i = 0; i < callBacks.length; i++) callBacks[i]();
        };
        if (options.singleCallback) callBacks.push(options.singleCallback);

        this.addCallbackToQueue = function(isComplete, callback) {
            if (isComplete) requestsCompleted++;
            if (callback) callBacks.push(callback);
            if (requestsCompleted == numRequestToComplete) fireCallbacks();
        };
        this.requestComplete = function(isComplete) {
            if (isComplete) requestsCompleted++;
            if (requestsCompleted == numRequestToComplete) fireCallbacks();
        };
        this.setCallback = function(callback) {
            callBacks.push(callBack);
        };
    };
})();
subhaze
источник
1
Спасибо за отличный пример кода! Думаю, в остальном я смогу проделать свой путь. Еще раз спасибо!
MisterIsaak
Без проблем, рад, что помог! Я немного увлекся этим: у P есть еще кое-какие идеи. Я планирую обновить его до тех пор, пока не нужно указывать количество запросов при инициализации.
subhaze 06
Хорошо, я надеюсь, что да! Очень интересно, я бы предложил добавить возможность добавить дополнительный обратный вызов. Я сделал, и он идеально подходит для того, чем я хочу заниматься. Еще раз спасибо!
MisterIsaak 06
Я сделал, но забыл добавить его в случай использования: P используйте, setCallbackчтобы добавить больше в очередь. Хотя теперь, когда я думаю об этом ... я должен назвать это addCallbackили что-то в этом роде ... а не просто установить, так как он добавляется в очередь
subhaze
Не singleCallbackнужна ли переменная во второй строке? Или я что-то упустил?
nimcap 01
158

Похоже, у вас есть ответы на этот вопрос, однако я думаю, что здесь стоит упомянуть кое-что, что значительно упростит ваш код. jQuery представил $.whenв v1.5. Это выглядит как:

$.when($.ajax(...), $.ajax(...)).then(function (resp1, resp2) {
    //this callback will be fired once all ajax calls have finished.
});

Не видел здесь упомянутого, надеюсь, это поможет.

Greg
источник
6
Спасибо за ответ, это определенно мой путь. он использует jQuery, он компактный, короткий и выразительный.
nimcap 01
2
Ответ Subhaze великолепен, но на самом деле это правильный.
Molomby
9
Я согласен, что это хорошее решение, если у вас есть jQuery 1.5+, но на момент ответа отложенная функциональность была недоступна. :(
subhaze
13

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

$('#button').click(function() {
    var inProgress = 0;

    function handleBefore() {
        inProgress++;
    };

    function handleComplete() {
        if (!--inProgress) {
            // do what's in here when all requests have completed.
        }
    };

    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
});
Matt
источник
Это очень просто и отлично работает ... Не могли бы вы немного лучше объяснить, что происходит в if (--inProgress)? Он должен контролировать, если переменная inProgress == 0, и вычитать из нее одну единицу, но я не понимаю компактного синтаксиса ...
Питто
1
@Pitto: Ой ... на самом деле там логическая ошибка, и она должна быть if (!--inProgress). Правильная версия аналогична inProgress = inProgress - 1; if (inProgress == 0) { /* do stuff */ }. Компактная форма комбинирует префиксный оператор декремента ( --) и оператор отрицания ( !), чтобы получить значение «истина» (и, следовательно, выполнить ifблок), если inProgressрезультат декремента inProgress == 0, и получить значение «ложь» (и, следовательно, ничего не делать) в противном случае.
Мэтт
Просто замечательно! Спасибо за ваше время и терпение.
Pitto
@Pitto: Хммм ... ты можешь дать ссылку на код, который ты используешь? У меня работает .
Мэтт
10

Стоит отметить, что, поскольку $.whenожидает, что все запросы ajax будут последовательными аргументами (а не массивом), вы обычно увидите$.when использовать .apply()такие:

// Save all requests in an array of jqXHR objects
var requests = arrayOfThings.map(function(thing) {
    return $.ajax({
        method: 'GET',
        url: 'thing/' + thing.id
    });
});
$.when.apply(this, requests).then(function(resp1, resp2/*, ... */) {
    // Each argument is an array with the following structure: [ data, statusText, jqXHR ]
    var responseArgsArray = Array.prototype.slice.call(this, arguments);

});

Это потому что $.when принимает такие аргументы

$.when(ajaxRequest1, ajaxRequest2, ajaxRequest3);

И не так:

$.when([ajaxRequest1, ajaxRequest2, ajaxRequest3]);
Кори Дэниэлсон
источник
Отличный ответ. Сейчас есть много хороших многообещающих библиотек, в большинстве из них есть allметод или что-то подобное, но мне нравится концепция карты. Ницца.
Грег
2
Да, я почти всегда использую $ .when, применяя к нему массив запросов, и не заметил этого в решении, поэтому просто добавил его, чтобы получить здесь хороший пример.
Кори Дэниэлсон,
4

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

var sync = {
 callbacksToComplete = 3,
 callbacksCompleted = 0,
 addCallbackInstance = function(){
  this.callbacksCompleted++;
  if(callbacksCompleted == callbacksToComplete) {
   doFinalCallBack();
  }
 }
};

[Отредактировано с учетом обновленных названий.]

Адольф Трюдо
источник
^ Согласен, помог лучше понять, что мне действительно нужно.
MisterIsaak
3

ИЗМЕНИТЬ - возможно, лучшим вариантом было бы создать конечную точку службы, которая делает все, что делают три запроса. Таким образом, вам нужно выполнить только один запрос, и все данные будут там, где вам нужно, в ответе. Если вы обнаружите, что выполняете одни и те же 3 запроса снова и снова, вы, вероятно, захотите пойти по этому пути. Часто хорошим дизайнерским решением является установка на сервере фасадной службы, которая объединяет вместе часто используемые более мелкие серверные действия. Просто идея.


один из способов сделать это - создать объект «sync» в обработчике кликов перед вызовами ajax. Что-то вроде

var sync = {
   count: 0
}

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

В качестве альтернативы вы можете сделать что-то вроде

var sync = {
   success1Complete: false,
   ...
   success3Complete: false,
}

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

Обратите внимание на случай, когда один из ваших xhrs не возвращает успех - вы должны это учитывать.

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

hvgotcodes
источник
Мне больше нравится ваше первое предложение по нескольким причинам. У каждого запроса есть своя цель, поэтому я хотел бы разделить их по этой причине. Кроме того, запросы фактически находятся в функциях, потому что я использую их в другом месте, поэтому я пытался сохранить модульный дизайн, если это возможно. Спасибо за вашу помощь!
MisterIsaak
@Jisaak, да, размещение метода на сервере для решения этой проблемы может не иметь для вас смысла. Но допустимо установить фасад на сервере. Вы говорите о модульности, создание одного метода, который обрабатывает все три вещи, является модульным.
hvgotcodes 06
0

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

// lets say we have 2 ajax functions that needs to be "synchronized". 
// In other words, we want to know when both are completed.
function foo1(callback) {
    $.ajax({
        url: '/echo/html/',
        success: function(data) {
            alert('foo1');
            callback();               
        }
    });
}

function foo2(callback) {
    $.ajax({
        url: '/echo/html/',
        success: function(data) {
            alert('foo2');
            callback();
        }
    });
}

// here is my simplified solution
ajaxSynchronizer = function() {
    var funcs = [];
    var funcsCompleted = 0;
    var callback;

    this.add = function(f) {
        funcs.push(f);
    }

    this.synchronizer = function() {
        funcsCompleted++;
        if (funcsCompleted == funcs.length) {
            callback.call(this);
        }
    }

    this.callWhenFinished = function(cb) {
        callback = cb;
        for (var i = 0; i < funcs.length; i++) {
            funcs[i].call(this, this.synchronizer);
        }
    }
}

// this is the function that is called when both ajax calls are completed.
afterFunction = function() {
    alert('All done!');
}

// this is how you set it up
var synchronizer = new ajaxSynchronizer();
synchronizer.add(foo1);
synchronizer.add(foo2);
synchronizer.callWhenFinished(afterFunction);

Здесь есть некоторые ограничения, но в моем случае это было нормально. Я также обнаружил, что для более сложных вещей есть также плагин AOP (для jQuery), который может быть полезен: http://code.google.com/p/jquery-aop/

PålOliver
источник
0

Я столкнулся с этой проблемой сегодня, и это была моя наивная попытка до того, как посмотреть принятый ответ.

<script>
    function main() {
        var a, b, c
        var one = function() {
            if ( a != undefined  && b != undefined && c != undefined ) {
                alert("Ok")
            } else {
                alert( "¬¬ ")
            }
        }

        fakeAjaxCall( function() {
            a = "two"
            one()
        } )
        fakeAjaxCall( function() {
            b = "three"
            one()
        } )
        fakeAjaxCall( function() {
            c = "four"
            one()
        } )
    }
    function fakeAjaxCall( a ) {
        a()
    }
    main()
</script>
OscarRyz
источник
0

Это не jquery (и, похоже, у jquery есть работоспособное решение), а просто как еще один вариант ....

У меня были похожие проблемы, когда я много работал с веб-службами SharePoint - вам часто нужно извлекать данные из нескольких источников для создания входных данных для одного процесса.

Чтобы решить эту проблему, я встроил такую ​​функциональность в свою библиотеку абстракций AJAX. Вы можете легко определить запрос, который будет запускать набор обработчиков по завершении. Однако каждый запрос может быть определен с помощью нескольких HTTP-вызовов. Вот компонент (и подробная документация):

DPAJAX на DepressedPress.com

Этот простой пример создает один запрос с тремя вызовами, а затем передает эту информацию в порядке вызова одному обработчику:

    // The handler function 
function AddUp(Nums) { alert(Nums[1] + Nums[2] + Nums[3]) }; 

    // Create the pool 
myPool = DP_AJAX.createPool(); 

    // Create the request 
myRequest = DP_AJAX.createRequest(AddUp); 

    // Add the calls to the request 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [5,10]); 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [4,6]); 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [7,13]); 

    // Add the request to the pool 
myPool.addRequest(myRequest); 

Обратите внимание, что в отличие от многих других решений (в том числе, я считаю, что решение «когда» в jquery) при условии, что этот метод не вызывает однопоточную обработку выполняемых вызовов - каждое из них по-прежнему будет выполняться так быстро (или так медленно), как позволяет среда. но единственный обработчик будет вызываться только тогда, когда все будут завершены. Он также поддерживает установку значений тайм-аута и повторных попыток, если ваша служба немного нестабильна.

Я нашел его безумно полезным (и невероятно простым для понимания с точки зрения кода). Больше никаких цепочек, никаких подсчетов вызовов и сохранения вывода. Просто «установи и забудь».

Джим Дэвис
источник
0

Хорошо, это устарело, но позвольте мне поделиться своим решением :)

function sync( callback ){
    syncCount--;
    if ( syncCount < 1 ) callback();
}
function allFinished(){ .............. }

window.syncCount = 2;

$.ajax({
    url: 'url',
    success: function(data) {
        sync( allFinished );
    }
});
someFunctionWithCallback( function(){ sync( allFinished ); } )

Он также работает с функциями, у которых есть обратный вызов. Вы устанавливаете syncCount и вызываете функцию sync (...) в обратном вызове каждого действия.

так
источник
0

Я нашел более простой способ сделать это без необходимости в дополнительных методах организации очереди.

JS

$.ajax({
  type: 'POST',
  url: 'ajax1.php',
  data:{
    id: 1,
    cb:'method1'//declaration of callback method of ajax1.php
  },
  success: function(data){
  //catching up values
  var data = JSON.parse(data);
  var cb=data[0].cb;//here whe catching up the callback 'method1'
  eval(cb+"(JSON.stringify(data));");//here we calling method1 and pass all data
  }
});


$.ajax({
  type: 'POST',
  url: 'ajax2.php',
  data:{
    id: 2,
    cb:'method2'//declaration of callback method of ajax2.php
  },
  success: function(data){
  //catching up values
  var data = JSON.parse(data);
  var cb=data[0].cb;//here whe catching up the callback 'method2'
  eval(cb+"(JSON.stringify(data));");//here we calling method2 and pass all data
  }
});


//the callback methods
function method1(data){
//here we have our data from ajax1.php
alert("method1 called with data="+data);
//doing stuff we would only do in method1
//..
}

function method2(data){
//here we have our data from ajax2.php
alert("method2 called with data="+data);
//doing stuff we would only do in method2
//..
}

PHP (ajax1.php)

<?php
    //catch up callbackmethod
    $cb=$_POST['cb'];//is 'method1'

    $json[] = array(
    "cb" => $cb,
    "value" => "ajax1"
    );      

    //encoding array in JSON format
    echo json_encode($json);
?>

PHP (ajax2.php)

<?php
    //catch up callbackmethod
    $cb=$_POST['cb'];//is 'method2'

    $json[] = array(
    "cb" => $cb,
    "value" => "ajax2"
    );      

    //encoding array in JSON format
    echo json_encode($json);
?>
medicalbird
источник
-1
async   : false,

По умолчанию все запросы отправляются асинхронно (т. Е. По умолчанию установлено значение true). Если вам нужны синхронные запросы, установите эту опцию на false. Междоменные запросы и dataType: "jsonp"запросы не поддерживают синхронную работу. Обратите внимание, что синхронные запросы могут временно блокировать браузер, отключая любые действия, когда запрос активен. Начиная с jQuery 1.8 , использование async: falseс jqXHR ( $.Deferred) не рекомендуется; вы должны использовать параметры обратного вызова success / error / complete вместо соответствующих методов объекта jqXHR, таких как jqXHR.done()или устаревшие jqXHR.success().

Эстевес Кляйн
источник
Это не подходящее решение. Никогда не следует делать синхронный запрос Ajax.
user128216
-2
$.ajax({type:'POST', url:'www.naver.com', dataType:'text', async:false,
    complete:function(xhr, textStatus){},
    error:function(xhr, textStatus){},
    success:function( data ){
        $.ajax({type:'POST', 
            ....
            ....
            success:function(data){
                $.ajax({type:'POST',
                    ....
                    ....
            }
        }
    });

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

nicecue
источник
Не следует использовать ajax таким образом. Обратный звонок Ад ..!
traeper