jQuery callback pour plusieurs appels ajax

132

Je souhaite effectuer trois appels ajax dans un événement de clic. Chaque appel ajax effectue une opération distincte et renvoie les données nécessaires pour un rappel final. Les appels eux-mêmes ne dépendent pas les uns des autres, ils peuvent tous se dérouler en même temps, mais j'aimerais avoir un dernier rappel lorsque les trois sont terminés.

$('#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*/});
});
MonsieurIsaak
la source

Réponses:

112

Voici un objet de rappel que j'ai écrit dans lequel vous pouvez soit définir un seul rappel pour qu'il se déclenche une fois que tout est terminé, soit laisser chacun avoir son propre rappel et les déclencher une fois tous terminés:

REMARQUER

Depuis jQuery 1.5+, vous pouvez utiliser la méthode différée comme décrit dans une autre réponse:

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

Exemple de différé ici

pour jQuery <1.5, ce qui suit fonctionnera ou si vous avez besoin de déclencher vos appels ajax à des moments inconnus, comme indiqué ici avec deux boutons: déclenché après avoir cliqué sur les deux boutons

[usage]

pour un seul rappel une fois terminé: exemple de travail

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

chacun ayant son propre rappel lorsque tout est terminé: Exemple de travail

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

[Le code]

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);
        };
    };
})();
brouiller
la source
1
Merci pour cet excellent exemple de code! Je pense que je serai capable de trébucher sur le reste. Merci encore!
MisterIsaak
Aucun problème, heureux que cela ait aidé! Je me suis un peu emporté avec ça: j'ai encore d'autres idées pour ça. Je prévois de le mettre à jour là où vous n'avez pas à spécifier un certain nombre de requêtes lors de l'initialisation.
subhaze
Bien, j'espère que vous faites! Très intéressant, je suggérerais d'ajouter l'option d'ajouter un rappel supplémentaire. Je l'ai fait et cela fonctionne parfaitement pour ce que je veux faire. Merci encore!
MisterIsaak
J'ai fait mais j'ai oublié de l'ajouter au cas d'utilisation: P utiliser setCallbackpour en ajouter plus à la file d'attente. Bien que maintenant que j'y pense ... je devrais l'appeler addCallbackou quelque chose du genre ... et pas seulement définir car il s'ajoute à la file d'attente
subhaze
La singleCallbackvariable de la deuxième ligne est-elle inutile? Ou est-ce que je manque quelque chose?
nimcap
158

On dirait que vous avez des réponses à cela, mais je pense qu'il y a quelque chose qui mérite d'être mentionné ici qui simplifiera grandement votre code. jQuery a introduit le $.whendans la v1.5. On dirait:

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

Je ne l'ai pas vu mentionné ici, j'espère que cela aide.

Greg
la source
6
Merci pour la réponse, c'est définitivement la voie à suivre pour moi. il utilise jQuery, il est compact, court et expressif.
nimcap
2
La réponse de Subhaze est excellente mais vraiment, c'est la bonne.
Molomby
9
Je suis d'accord que c'est une bonne solution lorsque vous avez jQuery 1.5+ mais qu'au moment de la réponse, la fonctionnalité différée n'était pas disponible. :(
subhaze
13

Ne voyant pas le besoin d'un objet malarky moi-même. Simple a une variable qui est un entier. Lorsque vous démarrez une demande, augmentez le nombre. Quand on termine, décrémente-le. Lorsqu'il est nul, il n'y a aucune demande en cours, donc vous avez terminé.

$('#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
        }
    });
});
Mat
la source
Ceci est très simple et fonctionne parfaitement ... Pourriez-vous expliquer un peu mieux ce qui se passe dans if (--inProgress)? Il devrait contrôler si la variable inProgress == 0 et lui soustraire une unité mais je n'obtiens pas la syntaxe compacte ...
Pitto
1
@Pitto: Oups ... en fait, il y a une erreur de logique là-dedans, et ça devrait être if (!--inProgress). La version correcte est analogue à inProgress = inProgress - 1; if (inProgress == 0) { /* do stuff */ }. La forme compacte combine l' opérateur de décrémentation de préfixe ( --) et l'opérateur de négation ( !) pour évaluer à «vrai» (et donc exécuter le ifbloc), si la décrémentation inProgressaboutit inProgress == 0, et s'évalue à «faux» (et donc ne fait rien) autrement.
Matt
Tout simplement génial! Merci pour votre temps et votre patience.
Pitto
@Pitto: Humm .. pouvez-vous créer un lien vers le code que vous utilisez? Cela fonctionne pour moi .
Matt
10

Il est intéressant de noter que puisque $.whens'attend à toutes les requêtes ajax en tant qu'arguments séquentiels (pas un tableau), vous verrez couramment $.whenutilisé .apply()comme ceci:

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

});

C'est parce que $.whenaccepte des arguments comme celui-ci

$.when(ajaxRequest1, ajaxRequest2, ajaxRequest3);

Et pas comme ça:

$.when([ajaxRequest1, ajaxRequest2, ajaxRequest3]);
Cory Danielson
la source
Excellente réponse. Il y a beaucoup de bonnes bibliothèques prometteuses maintenant, la plupart d'entre elles ont une allméthode ou quelque chose de similaire, mais j'aime le concept de carte. Agréable.
Greg
2
Oui, j'utilise presque toujours $ .quand en appliquant un tableau de requêtes dessus, et je ne l'ai pas repéré dans une solution ici, alors je l'ai simplement ajouté pour avoir un bon exemple ici.
Cory Danielson
4

J'aime l'idée de hvgotcodes. Ma suggestion est d'ajouter un incrémenteur générique qui compare le nombre complet au nombre nécessaire, puis exécute le rappel final. Cela pourrait être intégré dans le rappel final.

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

[Modifié pour refléter les mises à jour de nom.]

Adolph Trudeau
la source
^ D'accord, cela m'a aidé à mieux comprendre ce dont j'avais réellement besoin.
MisterIsaak
3

EDIT - la meilleure option serait peut-être de créer un point de terminaison de service qui fait tout ce que font les trois demandes. De cette façon, vous n'avez qu'à faire une seule demande, et toutes les données sont là où vous en avez besoin dans la réponse. Si vous constatez que vous faites les mêmes 3 demandes encore et encore, vous voudrez probablement emprunter cette voie. Il est souvent judicieux de mettre en place un service de façade sur le serveur qui regroupe les actions de serveur plus petites couramment utilisées. Juste une idée.


une façon de le faire serait de créer un objet 'sync' dans votre gestionnaire de clics avant les appels ajax. Quelque chose comme

var sync = {
   count: 0
}

La synchronisation sera automatiquement liée à la portée des appels réussis (fermeture). Dans le gestionnaire de réussite, vous incrémentez le nombre et s'il est égal à 3, vous pouvez appeler l'autre fonction.

Sinon, vous pouvez faire quelque chose comme

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

lorsque chaque succès est exécuté, la valeur de la synchronisation devient true. Vous devrez vérifier la synchronisation pour vous assurer que les trois sont vrais avant de continuer.

Notez le cas où l'un de vos xhrs ne renvoie pas de succès - vous devez en tenir compte.

Encore une autre option serait d'appeler toujours la fonction finale dans vos gestionnaires de succès et de lui faire accéder à l'option de synchronisation pour déterminer s'il faut réellement faire quelque chose. Vous devrez cependant vous assurer que la synchronisation est dans le cadre de cette fonction.

hvgotcodes
la source
J'aime davantage votre première suggestion pour plusieurs raisons. Chaque demande a un but distinct, alors j'aimerais les garder séparées pour cette raison. De plus, les demandes sont en fait dans une fonction car je les utilise ailleurs, donc j'essayais de garder une conception modulaire si possible. Merci de votre aide!
MisterIsaak
@Jisaak, ouais, mettre une méthode sur le serveur pour gérer cela pourrait ne pas avoir de sens pour vous. Mais il est acceptable de mettre en place une façade sur le serveur. Vous parlez de modularité, créer une méthode qui gère les trois choses est modulaire.
hvgotcodes
0

J'ai eu de bons indices à partir des réponses sur cette page. Je l'ai un peu adapté à mon usage et j'ai pensé pouvoir partager.

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

Il y a quelques limitations ici, mais pour mon cas c'était correct. J'ai également trouvé que pour des choses plus avancées, il existe également un plugin AOP (pour jQuery) qui pourrait être utile: http://code.google.com/p/jquery-aop/

PålOliver
la source
0

Je suis tombé sur ce problème aujourd'hui, et c'était ma tentative naïve avant de regarder la réponse acceptée.

<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
la source
0

Ce n'est pas jquery (et il semble que jquery a une solution viable) mais juste comme une autre option ...

J'ai eu des problèmes similaires avec les services Web SharePoint: vous devez souvent extraire des données de plusieurs sources pour générer des entrées pour un seul processus.

Pour le résoudre, j'ai intégré ce type de fonctionnalité dans ma bibliothèque d'abstraction AJAX. Vous pouvez facilement définir une requête qui déclenchera un ensemble de gestionnaires une fois terminée. Cependant, chaque requête peut être définie avec plusieurs appels http. Voici le composant (et la documentation détaillée):

DPAJAX sur DepressedPress.com

Cet exemple simple crée une demande avec trois appels, puis transmet ces informations, dans l'ordre des appels, à un seul gestionnaire:

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

Notez que contrairement à la plupart des autres solutions (y compris, je crois, la solution "quand" dans jquery) à condition que cette méthode ne force pas le threading unique des appels en cours - chacune fonctionnera toujours aussi rapidement (ou aussi lentement) que l'environnement le permet mais le gestionnaire unique ne sera appelé que lorsque tout sera terminé. Il prend également en charge la définition des valeurs de délai d'expiration et les tentatives de relance si votre service est un peu floconneux.

Je l'ai trouvé incroyablement utile (et incroyablement simple à comprendre du point de vue du code). Plus de chaînage, plus de comptage des appels et d'économie de sortie. Il suffit de régler et oublier".

Jim Davis
la source
0

Ok, c'est vieux mais laissez-moi vous apporter ma solution :)

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

Cela fonctionne également avec les fonctions qui ont un rappel. Vous définissez le syncCount et vous appelez la fonction sync (...) dans le rappel de chaque action.

sic
la source
0

J'ai trouvé un moyen plus simple de le faire sans avoir besoin de méthodes supplémentaires pour organiser une file d'attente.

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);
?>
oiseau médical
la source
-1
async   : false,

Par défaut, toutes les demandes sont envoyées de manière asynchrone (c'est-à-dire que ce paramètre est défini sur true par défaut). Si vous avez besoin de requêtes synchrones, définissez cette option sur false. Les requêtes et dataType: "jsonp"requêtes entre domaines ne prennent pas en charge le fonctionnement synchrone. Notez que les demandes synchrones peuvent temporairement verrouiller le navigateur, désactivant toutes les actions pendant que la demande est active. Depuis jQuery 1.8 , l'utilisation de async: falseavec jqXHR ( $.Deferred) est obsolète; vous devez utiliser les options de rappel success / error / complete au lieu des méthodes correspondantes de l' objet jqXHR telles que jqXHR.done()or the deprecated jqXHR.success().

Esteves Klein
la source
Ce n’est pas une solution adéquate. Vous ne devriez jamais faire une requête Ajax synchrone.
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',
                    ....
                    ....
            }
        }
    });

Je suis désolé mais je ne peux pas expliquer ce que je veux parce que je suis un coréen qui ne peut même pas parler un mot en anglais. mais je pense que vous pouvez facilement le comprendre.

gentil
la source
Ne devrait pas utiliser ajax de cette façon. Callback Hell ..!
traeper