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


Відповіді:


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);
        };
    };
})();

1
Дякуємо за чудовий приклад коду! Я думаю, що мені вдасться наткнутися через решту. Знову дякую!
MisterIsaak

Жодної проблеми, радий, що це допомогло! Мене якось захопило це: P все ще є ще кілька ідей для цього. Планую оновити його там, де вам не потрібно вказувати ряд запитів щодо ініціалізації.
підгалузь

Приємно, я сподіваюся, що ви зробите! Дуже цікаво, я б запропонував включити опцію, щоб додати додатковий зворотний дзвінок. Я це зробив, і це ідеально підходить для того, що я хочу зробити. Знову дякую!
MisterIsaak

Я це зробив, але забув додати його до випадку використання: P використовуйте, setCallbackщоб додати більше до черги. Хоча тепер, коли я замислююся над цим ... я повинен назвати це addCallbackчи щось подібне ... а не просто встановити, оскільки він додає до черги
підзаголовок

Чи singleCallbackне потрібна змінна у другому рядку? Або я щось пропускаю?
nimcap

158

Схоже, у вас є відповіді на це, однак, я думаю, що тут варто щось згадати, що значно спростить ваш код. jQuery представив $.whenв v1.5. Це виглядає як:

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

Не бачила цього згадуваного тут, сподіваюся, що це допомагає.


6
Дякую за відповідь, це безумовно шлях для мене. він використовує jQuery, він компактний, короткий і виразний.
nimcap

2
Відповідь Subhaze чудова, але насправді, це правильна.
Моломбі

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

Це дуже просто і працює чудово ... Ви б не хотіли б пояснити трохи краще, що станеться, якщо (--inProgress)? Він повинен контролювати, якщо змінна inProgress == 0, і віднімати до неї одну одиницю, але я не отримую компактний синтаксис ...
Пітто,

1
@Pitto: На жаль, насправді там є логічна помилка, і вона повинна бути if (!--inProgress). Правильна версія аналогічна inProgress = inProgress - 1; if (inProgress == 0) { /* do stuff */ }. Компактна форма поєднує в собі оператор скорочення префікса ( --) і оператор заперечення ( !) для оцінки "істинного" (і, отже, виконання ifблоку), якщо декрементування inProgressпризводить до inProgress == 0, і оцінює "помилковим" (і, отже, нічого не робить) інакше.
Метт

Просто woderful! Дякуємо за ваш час та терпіння.
Пітто

@Pitto: Humm .. чи можете ви зв’язати код, який ви використовуєте? Це працює для мене .
Метт

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
Так, я майже завжди використовую $., Коли застосовую до нього масив запитів, і не помічаю цього в рішенні, тому просто додав його, щоб мати хороший приклад тут.
Cori Danielson

4

Мені подобається ідея hvgotcodes. Моя пропозиція - додати загальний інкремент, який порівнює число, повне до потрібного, а потім запускає остаточний зворотний виклик. Це може бути вбудовано в остаточний зворотний виклик.

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

[Відредаговано для відображення оновлень імен.]


^ Домовився, допоміг краще зрозуміти, що мені насправді потрібно.
MisterIsaak

3

EDIT - можливо, найкращим варіантом було б створити службову кінцеву точку, яка виконує всі три запити. Таким чином вам потрібно зробити лише один запит, і всі дані - це там, де вам це потрібно, щоб вони були у відповіді. Якщо ви виявите, що ви робите однакові 3 запити знову і знову, ви, ймовірно, захочете пройти цей маршрут. Часто хорошим дизайнерським рішенням є створення сервісу фасадів на сервері, який грудочки зазвичай використовує дрібніші дії сервера разом. Просто ідея.


один із способів зробити це - створити об’єкт "синхронізації" у вашому оброблювачем кліків до виклику Ajax. Щось на зразок

var sync = {
   count: 0
}

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

Як варіант, ви можете зробити щось на кшталт

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

коли кожен успіх виконується, це змінює значення синхронізації на справжнє. Вам слід перевірити синхронізацію, щоб переконатися, що всі три справжні, перш ніж продовжувати.

Зверніть увагу на випадок, коли один з ваших xhrs не повертає успіх - вам потрібно це врахувати.

Ще одним варіантом буде завжди викликати остаточну функцію у ваших обробниках успіху, і мати їй доступ до параметра синхронізації, щоб визначити, чи дійсно щось робити. Вам потрібно переконатися, що синхронізація знаходиться в межах цієї функції.


Мені подобається ваша перша пропозиція більше з кількох причин. Кожен запит має чітке призначення, тому я хотів би тримати їх окремо з цієї причини. Також запити фактично є у функціях, оскільки я їх використовую в інших місцях, тому я намагався зберегти модульну конструкцію, якщо це можливо. Спасибі за вашу допомогу!
MisterIsaak

@Jisaak, так, розміщення методу на сервері для вирішення цього питання може не мати для вас сенсу. Але допустимо встановити фасад на сервері. Ви говорите про модульність, створивши один метод, який обробляє всі три речі, модульний.
hvgotcodes


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/


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>

0

Це не jquery (і, здається, jquery має працездатне рішення), а просто як інший варіант ....

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

Для її вирішення я вбудував такий функціонал у свою бібліотеку абстракцій AJAX. Ви можете легко визначити запит, який запустить набір обробників після завершення. Однак кожен запит можна визначити за допомогою декількох http-дзвінків. Ось компонент (і детальна документація):

DPAJAX на DepressPress.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);
?>

-1
async   : false,

За замовчуванням усі запити надсилаються асинхронно (тобто за замовчуванням встановлено значення true). Якщо вам потрібні синхронні запити, встановіть цю опцію на false. Міждоменні запити та dataType: "jsonp"запити не підтримують синхронну роботу. Зауважте, що синхронні запити можуть тимчасово заблокувати браузер, відключаючи будь-які дії під час активного запиту. За JQuery 1.8 , використання async: falseз jqXHR ( $.Deferred) є застарілим; ви повинні використовувати параметри зворотного виклику успіх / помилка / повний виклик замість відповідних методів об’єкта jqXHR, таких як jqXHR.done()або застарілий jqXHR.success().


Це не адекватне рішення. Ніколи не слід робити синхронний запит Ajax.
користувач128216

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

Вибачте, але не можу пояснити, що я роблю, тому що я корейця, який не може говорити ні слова англійською. але я думаю, ти можеш це легко зрозуміти.


Не слід використовувати Ajax таким чином. Відклик пекла ..!
трапер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.