Comment utiliser jQuery deferred?

jQuery 1.5 apporte le nouvel objet différé et les méthodes attachées .when , .Deferred et ._Deferred .

pour ceux qui n'ont pas utilisé .Deferred avant que j'ai annoté la source pour it

quelles sont les utilisations possibles de ces nouvelles méthodes, comment les adapter à des modèles?

j'ai déjà lu le API et le source , donc je sais ce qu'il fait. Ma question Est de savoir comment utiliser ces nouvelles fonctionnalités dans le code de tous les jours?

j'ai un simple exemple d'une classe buffer qui appelle la requête AJAX dans l'ordre. (Prochain démarrage après le précédent se termine).

/* Class: Buffer
 *  methods: append
 *
 *  Constructor: takes a function which will be the task handler to be called
 *
 *  .append appends a task to the buffer. Buffer will only call a task when the 
 *  previous task has finished
 */
var Buffer = function(handler) {
    var tasks = [];
    // empty resolved deferred object
    var deferred = $.when();

    // handle the next object
    function handleNextTask() {
        // if the current deferred task has resolved and there are more tasks
        if (deferred.isResolved() && tasks.length > 0) {
            // grab a task
            var task = tasks.shift();
            // set the deferred to be deferred returned from the handler
            deferred = handler(task);
            // if its not a deferred object then set it to be an empty deferred object
            if (!(deferred && deferred.promise)) {
                deferred = $.when();
            }
            // if we have tasks left then handle the next one when the current one 
            // is done.
            if (tasks.length > 0) {
                deferred.done(handleNextTask);
            }
        }
    }

    // appends a task.
    this.append = function(task) {
        // add to the array
        tasks.push(task);
        // handle the next task
        handleNextTask();
    };
};

je cherche des démonstrations et des utilisations possibles de .Deferred et .when .

il serait également agréable de voir des exemples de ._Deferred .

lien vers la nouvelle jQuery.ajax la source des exemples est tricher.

Bounty: nous montrer quelles techniques sont disponibles quand nous abstray loin si une opération est faite de façon synchrone ou asynchrone.

274
demandé sur Ufuk Hacıoğulları 2011-02-02 03:36:53

11 réponses

le meilleur cas d'utilisation que je peux penser est dans la mise en cache des réponses AJAX. Voici un exemple modifié de l'intro de Rebecca Murphey sur le thème :

var cache = {};

function getData( val ){

    // return either the cached value or jqXHR object wrapped Promise
    return $.when(
        cache[ val ] || 
        $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json',
            success: function( resp ){
                cache[ val ] = resp;
            }
        })
    );
}

getData('foo').then(function(resp){
    // do something with the response, which may
    // or may not have been retrieved using an
    // XHR request.
});

essentiellement, si la valeur a déjà été demandée une fois avant d'être retournée immédiatement du cache. Sinon, une requête AJAX récupère les données et les ajoute au cache. Le $.when / .then ne se soucie pas de tout cela; tout ce que vous devez être préoccupé par utilise la réponse, qui est transmise au gestionnaire .then() dans les deux cas. jQuery.when() traite un non-promesse/différé comme un terminé, immédiatement l'exécution de tout .done() ou .then() sur la chaîne.

Déferrés sont parfaits pour quand la tâche peut ou ne peut pas fonctionner asynchrone, et vous voulez abstraire cette condition hors du code.

un autre exemple du monde réel utilisant le helper $.when :

$.when($.getJSON('/some/data/'), $.get('template.tpl')).then(function (data, tmpl) {

    $(tmpl) // create a jQuery object out of the template
    .tmpl(data) // compile it
    .appendTo("#target"); // insert it into the DOM

});
208
répondu ehynds 2016-12-28 15:44:30

Voici une implémentation légèrement différente d'un cache AJAX comme dans réponse de ehynd .

tel que noté dans question de suivi de fortuneRice , la mise en œuvre d'ehynd n'a pas réellement empêché plusieurs requêtes identiques si les requêtes ont été effectuées avant que l'une d'elles ne soit retournée. C'est-à-dire,

for (var i=0; i<3; i++) {
    getData("xxx");
}

donnera très probablement 3 requêtes AJAX si le résultat pour "xxx" n'a pas déjà été mis en cache avant.

ceci peut être résolu en cachant les Déféreurs de la requête au lieu du résultat:

var cache = {};

function getData( val ){

    // Return a promise from the cache (if available)
    // or create a new one (a jqXHR object) and store it in the cache.
    var promise = cache[val];
    if (!promise) {
        promise = $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json'
        });
        cache[val] = promise;
    }
    return promise;
}

$.when(getData('foo')).then(function(resp){
    // do something with the response, which may
    // or may not have been retreived using an
    // XHR request.
});
79
répondu Julian D. 2017-05-23 10:31:34

Un différé peut être utilisé à la place d'un mutex. C'est essentiellement le même que les multiples ajax scénarios d'utilisation.

MUTEX

var mutex = 2;

setTimeout(function() {
 callback();
}, 800);

setTimeout(function() {
 callback();
}, 500);

function callback() {
 if (--mutex === 0) {
  //run code
 }
}

différé

function timeout(x) {
 var dfd = jQuery.Deferred();
 setTimeout(function() {
  dfd.resolve();
 }, x);
 return dfd.promise();
}

jQuery.when(
timeout(800), timeout(500)).done(function() {
 // run code
});

lorsqu'on utilise un mutex différé uniquement, attention aux impacts de performance (http://jsperf.com/deferred-vs-mutex/2). Bien que la commodité, ainsi que les avantages supplémentaires fournis par un différé est l'impact sur les performances ne devrait pas être perceptible.

43
répondu user406905 2011-05-23 18:44:25

c'est une réponse auto-promotionnelle, mais j'ai passé quelques mois à faire des recherches et à présenter les résultats à jQuery Conference San Francisco 2012.

Voici une vidéo gratuite de la conférence:

http://www.confreaks.com/videos/993-jqcon2012-i-promise-to-show-you-when-to-use-deferreds

28
répondu Alex Mcp 2012-11-19 21:09:11

un autre usage que j'ai mis à bon escient est de récupérer des données à partir de plusieurs sources. Dans l'exemple ci-dessous, je récupère plusieurs objets JSON schema indépendants utilisés dans une application existante pour la validation entre un client et un serveur REST. Dans ce cas, je ne veux pas que l'application côté navigateur commence à charger des données avant qu'elle ait tous les schémas chargés. $.lorsque.appliquer.)(alors() est parfait pour cela. Merci à Raynos pour les conseils sur l'utilisation de then (fn1, fn2) pour surveiller les conditions d'erreur.

fetch_sources = function (schema_urls) {
    var fetch_one = function (url) {
            return $.ajax({
                url: url,
                data: {},
                contentType: "application/json; charset=utf-8",
                dataType: "json",
            });
        }
    return $.map(schema_urls, fetch_one);
}

var promises = fetch_sources(data['schemas']);
$.when.apply(null, promises).then(

function () {
    var schemas = $.map(arguments, function (a) {
        return a[0]
    });
    start_application(schemas);
}, function () {
    console.log("FAIL", this, arguments);
});     
20
répondu Elf Sternberg 2013-10-01 12:09:25

un autre exemple utilisant Deferred s pour implémenter un cache pour n'importe quel type de calcul (généralement des tâches exigeant des performances élevées ou de longue durée):

var ResultsCache = function(computationFunction, cacheKeyGenerator) {
    this._cache = {};
    this._computationFunction = computationFunction;
    if (cacheKeyGenerator)
        this._cacheKeyGenerator = cacheKeyGenerator;
};

ResultsCache.prototype.compute = function() {
    // try to retrieve computation from cache
    var cacheKey = this._cacheKeyGenerator.apply(this, arguments);
    var promise = this._cache[cacheKey];

    // if not yet cached: start computation and store promise in cache 
    if (!promise) {
        var deferred = $.Deferred();
        promise = deferred.promise();
        this._cache[cacheKey] = promise;

        // perform the computation
        var args = Array.prototype.slice.call(arguments);
        args.push(deferred.resolve);
        this._computationFunction.apply(null, args);
    }

    return promise;
};

// Default cache key generator (works with Booleans, Strings, Numbers and Dates)
// You will need to create your own key generator if you work with Arrays etc.
ResultsCache.prototype._cacheKeyGenerator = function(args) {
    return Array.prototype.slice.call(arguments).join("|");
};

voici un exemple d'utilisation de cette classe pour effectuer certains calculs (lourds simulés):

// The addingMachine will add two numbers
var addingMachine = new ResultsCache(function(a, b, resultHandler) {
    console.log("Performing computation: adding " + a + " and " + b);
    // simulate rather long calculation time by using a 1s timeout
    setTimeout(function() {
        var result = a + b;
        resultHandler(result);
    }, 1000);
});

addingMachine.compute(2, 4).then(function(result) {
    console.log("result: " + result);
});

addingMachine.compute(1, 1).then(function(result) {
    console.log("result: " + result);
});

// cached result will be used
addingMachine.compute(2, 4).then(function(result) {
    console.log("result: " + result);
});

le même cache sous-jacent pourrait être utilisé pour mettre en cache les requêtes Ajax:

var ajaxCache = new ResultsCache(function(id, resultHandler) {
    console.log("Performing Ajax request for id '" + id + "'");
    $.getJSON('http://jsfiddle.net/echo/jsonp/?callback=?', {value: id}, function(data) {
        resultHandler(data.value);
    });
});

ajaxCache.compute("anID").then(function(result) {
    console.log("result: " + result);
});

ajaxCache.compute("anotherID").then(function(result) {
    console.log("result: " + result);
});

// cached result will be used
ajaxCache.compute("anID").then(function(result) {
    console.log("result: " + result);
});

vous pouvez jouer avec le code ci-dessus dans ceci jsFiddle .

10
répondu Julian D. 2012-01-22 14:14:41

1) l'utiliser pour assurer une exécution ordonnée des callbacks:

var step1 = new Deferred();
var step2 = new Deferred().done(function() { return step1 });
var step3 = new Deferred().done(function() { return step2 });

step1.done(function() { alert("Step 1") });
step2.done(function() { alert("Step 2") });
step3.done(function() { alert("All done") });
//now the 3 alerts will also be fired in order of 1,2,3
//no matter which Deferred gets resolved first.

step2.resolve();
step3.resolve();
step1.resolve();

2) l'Utiliser pour vérifier l'état de l'application:

var loggedIn = logUserInNow(); //deferred
var databaseReady = openDatabaseNow(); //deferred

jQuery.when(loggedIn, databaseReady).then(function() {
  //do something
});
9
répondu Kernel James 2012-09-20 05:54:25

vous pouvez utiliser un objet différé pour faire un design fluide qui fonctionne bien dans les navigateurs webkit. Les navigateurs Webkit vont lancer redimensionner l'événement pour chaque pixel que la fenêtre est redimensionnée, contrairement à FF et IE qui ne déclenchent l'événement qu'une fois pour chaque redimensionnement. En conséquence, vous n'avez aucun contrôle sur l'ordre dans lequel les fonctions liées à votre événement de redimensionnement de fenêtre s'exécuteront. Quelque chose comme ça résout le problème:

var resizeQueue = new $.Deferred(); //new is optional but it sure is descriptive
resizeQueue.resolve();

function resizeAlgorithm() {
//some resize code here
}

$(window).resize(function() {
    resizeQueue.done(resizeAlgorithm);
});

cela sérialisera l'exécution de votre code ainsi qu'il s'exécute comme vous l'a voulu. Méfiez-vous des pièges en passant des méthodes objet comme callbacks à un différé. Une fois qu'une telle méthode est exécutée en tant que callback vers deferred, la référence 'this' sera écrasée avec référence à l'objet deferred et ne se référera plus à l'objet auquel la méthode appartient.

2
répondu Miloš Rašić 2011-03-01 12:27:06

vous pouvez également l'intégrer à n'importe quelle bibliothèque tierce qui utilise JQuery.

L'une de ces bibliothèques est Backbone, qui va en fait soutenir différé dans leur prochaine version. J'ai parlé aussi sur mon blog

2
répondu Diego 2012-11-22 05:34:20

je viens d'utiliser le différé en vrai code. Dans le projet jQuery Terminal j'ai fonction exec que les commandes d'appel définies par l'utilisateur (comme il l'entrait et en appuyant sur Entrée), j'ai ajouté des Déferrains à L'API et appel exec avec des tableaux. comme ceci:

terminal.exec('command').then(function() {
   terminal.echo('command finished');
});

ou

terminal.exec(['command 1', 'command 2', 'command 3']).then(function() {
   terminal.echo('all commands finished');
});

les commandes peuvent exécuter le code async, et exec doit appeler le code utilisateur dans l'ordre. Ma première api utilise une paire d'appels pause/resume et dans la nouvelle API Je les appelle automatiques lorsque l'utilisateur de retour promesse. Ainsi le code d'utilisateur peut juste utiliser

return $.get('/some/url');

ou

var d = new $.Deferred();
setTimeout(function() {
    d.resolve("Hello Deferred"); // resolve value will be echoed
}, 500);
return d.promise();

j'utilise le code suivant:

exec: function(command, silent, deferred) {
    var d;
    if ($.isArray(command)) {
        return $.when.apply($, $.map(command, function(command) {
            return self.exec(command, silent);
        }));
    }
    // both commands executed here (resume will call Term::exec)
    if (paused) {
        // delay command multiple time
        d = deferred || new $.Deferred();
        dalyed_commands.push([command, silent, d]);
        return d.promise();
    } else {
        // commands may return promise from user code
        // it will resolve exec promise when user promise
        // is resolved
        var ret = commands(command, silent, true, deferred);
        if (!ret) {
            if (deferred) {
                deferred.resolve(self);
                return deferred.promise();
            } else {
                d = new $.Deferred();
                ret = d.promise();
                ret.resolve();
            }
        }
        return ret;
    }
},

dalyed_commands est utilisé dans la fonction resume qui appelle exec à nouveau avec tous les dalyed_commands.

et une partie des commandes de fonction (je l'ai dépouillé de ne pas pièces connexes)

function commands(command, silent, exec, deferred) {

    var position = lines.length-1;
    // Call user interpreter function
    var result = interpreter.interpreter(command, self);
    // user code can return a promise
    if (result != undefined) {
        // new API - auto pause/resume when using promises
        self.pause();
        return $.when(result).then(function(result) {
            // don't echo result if user echo something
            if (result && position === lines.length-1) {
                display_object(result);
            }
            // resolve promise from exec. This will fire
            // code if used terminal::exec('command').then
            if (deferred) {
                deferred.resolve();
            }
            self.resume();
        });
    }
    // this is old API
    // if command call pause - wait until resume
    if (paused) {
        self.bind('resume.command', function() {
            // exec with resume/pause in user code
            if (deferred) {
                deferred.resolve();
            }
            self.unbind('resume.command');
        });
    } else {
        // this should not happen
        if (deferred) {
            deferred.resolve();
        }
    }
}
1
répondu jcubic 2014-06-02 07:34:17

la réponse d'ehynds ne fonctionnera pas, car elle cache les données des réponses. Il devrait mettre en cache le jqXHR qui est aussi une promesse. Voici le code correct:

var cache = {};

function getData( val ){

    // return either the cached value or an
    // jqXHR object (which contains a promise)
    return cache[ val ] || $.ajax('/foo/', {
        data: { value: val },
        dataType: 'json',
        success: function(data, textStatus, jqXHR){
            cache[ val ] = jqXHR;
        }
    });
}

getData('foo').then(function(resp){
    // do something with the response, which may
    // or may not have been retreived using an
    // XHR request.
});

la réponse de Julian D. fonctionnera correctement et est une meilleure solution.

1
répondu John Berg 2015-01-28 07:44:50