30 votes

Utilisation de $.Deferred() avec des appels ajax imbriqués dans une boucle

J'ai passé beaucoup trop d'heures à chercher des questions similaires et à essayer des solutions, alors j'espère que quelqu'un a une solution.

En fait, je voudrais être informé de la fin d'une fonction a(). Le problème est que la fonction contient un appel ajax et une boucle qui appelle b(), qui contient à nouveau un appel ajax.

MIS À JOUR AVEC LE VIOLON : http://jsfiddle.net/hsyj7/1/

Comme cela :

// called by main()
function a() {
  return $.ajax("http://url1").pipe(function(data){
    for (var i = 0; i < 2; i++) {
      console.log('a called');
      b();
    }
  });
}

// called by a()
function b() {
  for (var i = 0; i < 2; i++) {
    $.ajax("http://url2", function(data){
      // do something
      console.log('b called');
    }
  }
}

function main(){
  $.when(a()).done(function(){
    console.log('all completed');
  });
}

Ce que j'aimerais voir alors, c'est que les deux appels à a() soient placés en haut de la page :

a called
b called
b called
a called
b called
b called
all completed

Au lieu de cela, j'obtiens

a called
all completed
b called
b called

Ou une de ses variantes.

Je suis conscient que le code ci-dessus manque de la fonctionnalité defer à la fois dans la boucle et dans b(). Dans certaines des variantes que j'ai essayées, le gestionnaire done() dans main() n'est jamais appelé.

Quelqu'un sait-il comment procéder ?

29voto

freakish Points 20067

Oui, en utilisant Deferred est le moyen d'y parvenir :

function a() {
    var def = $.Deferred();

    $.ajax("http://url1").done(function(data){
        var requests = [];

        for (var i = 0; i < 2; i++) {
             requests.push(b());
        }

        $.when.apply($, requests).then(function() { def.resolve(); });
    });

    return def.promise();
}

// called by a()
function b() {
    var def = $.Deferred(),
        requests = [];

    for (var i = 0; i < 2; i++) {
        requests.push($.ajax("http://url2").done(function(data){
            // do something
            console.log('b called');
        });
    }

    $.when.apply($, requests).then(function() { def.resolve(); });

    return def.promise();
}

function main(){
    $.when(a()).done(function(){
        console.log('all completed');
    });
}

//EDIT : Remplacé .pipe avec .done .

2voto

jAndy Points 93076

Vous pouvez utiliser un Tableau qui se trouve dans un contexte plus élevé pour pousser Promesse / Différé dans les objets. Vous pouvez alors utiliser jQuery.when à côté Function.prototype.apply pour transmettre toutes les entrées en tant qu'arguments.

(function() {
    var promises = [ ],
        when = Function.prototype.apply.bind( jQuery.when, null );

    function a() {
         promises.push($.ajax("http://url1").pipe(function(data){
             for (var i = 0; i < 2; i++) {
                 console.log('a called');
                 b();
             }
         }));

         return promises;
    }

    function b() {
        for (var i = 0; i < 2; i++) {
            promises.push($.ajax("http://url2", function(data) {
                // do something
                console.log('b called');
            }));
        }
    }

    function main() {
        promises = [ ];

        when( a() ).done(function(){
            console.log('all completed');
        });
    }
}());

2voto

Bergi Points 104242

La question est peut-être ancienne, mais comme il n'y a pas encore de solution correcte, je vais donner une réponse ici. Il enchaîne correctement les promesses en utilisant .then (auparavant, il s'agissait de .pipe ) pour obtenir le résultat souhaité :

function a() {
  return $.ajax("http://url1").done(function(data){
    console.log('a called');
  }).then(function(){
    return $.when(b(), b()); // no loop for simplicity
  });
}
function b() {
  return $.ajax("http://url2").done(function(data){
    console.log('b called');
  });
}

function main(){
  a().done(function(){
    console.log('all completed');
  }, function() {
    console.log('an error occured!');
  });
}

En fonction du résultat, des données devraient être disponibles où l'imbrication/la structure pourrait être modifiée, mais l'ordre général est correct.

1voto

Jamie Hutber Points 4666

Je pense que cela peut être corrigé avec des callbacks, mais un fiddle m'aurait vraiment aidé à vérifier pour vous.

 // called by main()
 function a(callback) {
   //set this to the number of loops that is going to happen
   var number = 2;
   return $.ajax("http://url1", function(data){
     console.log('a called');
     for (var i = 0; i < number ; i++) {
       b();
       if(number===i){
           callback();
       }
     }
   }
 }

 function main(){
    a(function(){
       //Function or code you want to run on completion.. 
    });
 }

Pardonnez-moi si cela ne fonctionne pas, mais je pense que c'est la bonne direction.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X