web-dev-qa-db-fra.com

Transmettre un tableau de Deferreds à $ .when ()

Voici un exemple artificiel de ce qui se passe: http://jsfiddle.net/adamjford/YNGcm/20/

HTML:

<a href="#">Click me!</a>
<div></div>

JavaScript:

function getSomeDeferredStuff() {
    var deferreds = [];

    var i = 1;
    for (i = 1; i <= 10; i++) {
        var count = i;

        deferreds.Push(
        $.post('/echo/html/', {
            html: "<p>Task #" + count + " complete.",
            delay: count
        }).success(function(data) {
            $("div").append(data);
        }));
    }

    return deferreds;
}

$(function() {
    $("a").click(function() {
        var deferreds = getSomeDeferredStuff();

        $.when(deferreds).done(function() {
            $("div").append("<p>All done!</p>");
        });
    });
});

Je veux "Tout est fait!" apparaître une fois toutes les tâches différées terminées, mais $.when() ne semble pas savoir comment gérer un tableau d'objets différés. "Terminé!" se produit en premier parce que le tableau n'est pas un objet différé, donc jQuery continue et suppose qu'il est juste terminé.

Je sais que l’on pourrait passer les objets dans la fonction comme $.when(deferred1, deferred2, ..., deferredX), mais on ignore combien d’objets différés il y aura lors de l’exécution du problème que j’essaie de résoudre.

411
adamjford

Pour passer un tableau de valeurs à la fonction any qui s'attend normalement à ce qu'ils soient des paramètres distincts, utilisez Function.prototype.apply. Dans ce cas, vous avez besoin des éléments suivants:

$.when.apply($, my_array).then( ___ );

Voir http://jsfiddle.net/YNGcm/21/

Dans ES6, vous pouvez utiliser l'opérateur ...spread à la place:

$.when(...my_array).then( ___ );

Dans les deux cas, puisqu'il est peu probable que vous sachiez à l'avance le nombre de paramètres formels requis par le gestionnaire .then, ce dernier devra traiter le tableau arguments afin de récupérer le résultat de chaque promesse.

684
Alnitak

Les solutions de contournement ci-dessus (merci!) Ne résolvent pas correctement le problème de récupération des objets fournis à la méthode resolve() du différé car jQuery appelle les rappels done() et fail() avec des paramètres individuels et non un tableau. Cela signifie que nous devons utiliser le pseudo-tableau arguments pour obtenir tous les objets résolus/rejetés renvoyés par le tableau de reports, ce qui est moche:

$.when.apply($,deferreds).then(function() {
     var objects=arguments; // The array of resolved objects as a pseudo-array
     ...
};

Puisque nous avons passé un tableau de différés, il serait bien de récupérer un tableau de résultats. Il serait également agréable de récupérer un tableau réel au lieu d'un pseudo-tableau afin que nous puissions utiliser des méthodes telles que Array.sort().

Voici une solution inspirée par la méthode when.all() de when.js / qui résout ces problèmes:

// Put somewhere in your scripting environment
if (typeof jQuery.when.all === 'undefined') {
    jQuery.when.all = function (deferreds) {
        return $.Deferred(function (def) {
            $.when.apply(jQuery, deferreds).then(
                function () {
                    def.resolveWith(this, [Array.prototype.slice.call(arguments)]);
                },
                function () {
                    def.rejectWith(this, [Array.prototype.slice.call(arguments)]);
                });
        });
    }
}

Maintenant, vous pouvez simplement passer un tableau de différés/promesses et récupérer un tableau d'objets résolus/rejetés dans votre rappel, comme suit:

$.when.all(deferreds).then(function(objects) {
    console.log("Resolved objects:", objects);
});
101
crispyduck

Vous pouvez appliquer la méthode when à votre tableau:

var arr = [ /* Deferred objects */ ];

$.when.apply($, arr);

Comment travaillez-vous avec un tableau de jQuery Deferreds?

38
Eli

Lorsque vous appelez plusieurs appels parallèles AJAX en parallèle, vous disposez de deux options pour gérer les réponses respectives.

  1. Utiliser un appel synchrone AJAX/l'un après l'autre/non recommandé
  2. Utilisez Promises' array et $.when qui accepte promises et son callback .done est appelé lorsque toutes les promises sont renvoyées avec réponses respectives.

Exemple

function ajaxRequest(capitalCity) {
   return $.ajax({
        url: 'https://restcountries.eu/rest/v1/capital/'+capitalCity,
        success: function(response) {
        },
        error: function(response) {
          console.log("Error")
        }
    });
}
$(function(){
   var capitalCities = ['Delhi', 'Beijing', 'Washington', 'Tokyo', 'London'];
   $('#capitals').text(capitalCities);

   function getCountryCapitals(){ //do multiple parallel ajax requests
      var promises = [];   
      for(var i=0,l=capitalCities.length; i<l; i++){
            var promise = ajaxRequest(capitalCities[i]);
            promises.Push(promise);
      }
  
      $.when.apply($, promises)
        .done(fillCountryCapitals);
   }
  
   function fillCountryCapitals(){
        var countries = [];
        var responses = arguments;
        for(i in responses){
            console.dir(responses[i]);
            countries.Push(responses[i][0][0].nativeName)
        }  
        $('#countries').text(countries);
   }
  
   getCountryCapitals()
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
  <h4>Capital Cities : </h4> <span id="capitals"></span>
  <h4>Respective Country's Native Names : </h4> <span id="countries"></span>
</div>

6
vinayakj

En guise d'alternative simple, qui ne nécessite pas $.when.apply ni une array, vous pouvez utiliser le modèle suivant pour générer une seule promesse pour plusieurs promesses parallèles:

promise = $.when(promise, anotherPromise);

par exemple.

function GetSomeDeferredStuff() {
    // Start with an empty resolved promise (or undefined does the same!)
    var promise;
    var i = 1;
    for (i = 1; i <= 5; i++) {
        var count = i;

        promise = $.when(promise,
        $.ajax({
            type: "POST",
            url: '/echo/html/',
            data: {
                html: "<p>Task #" + count + " complete.",
                delay: count / 2
            },
            success: function (data) {
                $("div").append(data);
            }
        }));
    }
    return promise;
}

$(function () {
    $("a").click(function () {
        var promise = GetSomeDeferredStuff();
        promise.then(function () {
            $("div").append("<p>All done!</p>");
        });
    });
});

Remarques:

  • J'ai compris celui-ci après avoir vu la chaîne de quelqu'un de façon séquentielle, en utilisant promise = promise.then(newpromise)
  • L'inconvénient est qu'il crée des objets prometteurs supplémentaires en coulisse et que les paramètres transmis à la fin ne sont pas très utiles (car ils sont imbriqués dans des objets supplémentaires). Pour ce que vous voulez bien que ce soit court et simple.
  • L'avantage est qu'il ne nécessite aucun tableau ou gestion de tableau.
5
Gone Coding

Je veux en proposer un autre en utilisant $ .each:

  1. Nous pouvons déclarer une fonction ajax comme:

    function ajaxFn(someData) {
        this.someData = someData;
        var that = this;
        return function () {
            var promise = $.Deferred();
            $.ajax({
                method: "POST",
                url: "url",
                data: that.someData,
                success: function(data) {
                    promise.resolve(data);
                },
                error: function(data) {
                    promise.reject(data);
                }
            })
            return promise;
        }
    }
    
  2. Partie du code où nous créons un tableau de fonctions avec ajax à envoyer:

    var arrayOfFn = [];
    for (var i = 0; i < someDataArray.length; i++) {
        var ajaxFnForArray = new ajaxFn(someDataArray[i]);
        arrayOfFn.Push(ajaxFnForArray);
    }
    
  3. Et appeler des fonctions avec l'envoi d'ajax:

    $.when(
        $.each(arrayOfFn, function(index, value) {
            value.call()
        })
    ).then(function() {
            alert("Cheer!");
        }
    )
    
3
Volodymyr Yasinskyi

Si vous transpilez et avez accès à ES6, vous pouvez utiliser la syntaxe spread qui applique spécifiquement chaque élément itérable d'un objet en tant qu'argument discret, comme le nécessite $.when().

$.when(...deferreds).done(() => {
    // do stuff
});

MDN Link - Sprt Syntax

1
relic

J'ai eu un cas très similaire où je publiais dans chaque boucle, puis en définissant le balisage HTML dans certains champs à partir des numéros reçus de la Ajax. Je devais ensuite faire la somme des valeurs (maintenant mises à jour) de ces champs et les placer dans un champ total.

Le problème était donc que j'essayais de faire une somme sur tous les numéros mais qu'aucune donnée n'était encore revenue des appels async ajax. J'avais besoin de compléter cette fonctionnalité dans quelques fonctions pour pouvoir réutiliser le code. Ma fonction externe attend les données avant de partir et de faire quelques tâches avec le DOM entièrement mis à jour.

    // 1st
    function Outer() {
        var deferreds = GetAllData();

        $.when.apply($, deferreds).done(function () {
            // now you can do whatever you want with the updated page
        });
    }

    // 2nd
    function GetAllData() {
        var deferreds = [];
        $('.calculatedField').each(function (data) {
            deferreds.Push(GetIndividualData($(this)));
        });
        return deferreds;
    }

    // 3rd
    function GetIndividualData(item) {
        var def = new $.Deferred();
        $.post('@Url.Action("GetData")', function (data) {
            item.html(data.valueFromAjax);
            def.resolve(data);
        });
        return def;
    }
0
Cameron Forward

Si vous utilisez angularJS ou une variante de la bibliothèque de promesses Q, vous disposez d'une méthode .all() qui résout ce problème.

var savePromises = [];
angular.forEach(models, function(model){
  savePromises.Push(
    model.saveToServer()
  )
});

$q.all(savePromises).then(
  function success(results){...},
  function failed(results){...}
);

voir l'API complète:

https://github.com/kriskowal/q/wiki/API-Reference#promiseall

https://docs.angularjs.org/api/ng/service/$q

0
mastaBlasta