web-dev-qa-db-fra.com

Comment jQuery différé peut-il être utilisé?

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_ auparavant, j'ai annoté le source .

Quels sont les usages possibles de ces nouvelles méthodes, comment pouvons-nous les adapter à des modèles?

J'ai déjà lu le API et le source , je sais donc ce qu'il fait. Ma question est comment pouvons-nous utiliser ces nouvelles fonctionnalités dans le code quotidien?

J'ai un simple exemple d'une classe de mémoire tampon qui appelle AJAX demande dans l'ordre. (Le suivant commence après le précédent).

_/* 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 recherche des démonstrations et des utilisations possibles de _.Deferred_ et _.when_.

Il serait également agréable de voir des exemples de _._Deferred_.

La liaison à la nouvelle source jQuery.ajax est un exemple.

Je suis particulièrement intéressé par les techniques disponibles lorsque nous déterminons si une opération est effectuée de manière synchrone ou asynchrone.

277
Raynos

Le meilleur des cas d'utilisation auquel je puisse penser est la mise en cache des réponses AJAX. Voici un exemple modifié de post d'intro de Rebecca Murphey sur le sujet :

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

Fondamentalement, si la valeur a déjà été demandée une fois avant d'être renvoyée immédiatement du cache. Sinon, une demande AJAX récupère les données et les ajoute au cache. Le $.when/.then ne se soucie de rien de tout cela. tout ce dont vous avez besoin est d'utiliser la réponse, qui est transmise au gestionnaire .then() dans les deux cas. jQuery.when() gère une promesse non différée/différée en tant que finalisée, en exécutant immédiatement toute .done() ou .then() de la chaîne.

Les différés sont parfaits lorsque la tâche peut ou non fonctionner de manière asynchrone et que vous souhaitez extraire cette condition du code.

Un autre exemple concret utilisant l’assistant $.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

});
210
ehynds

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

Comme indiqué dans la question suivante de de fortuneRice , la mise en œuvre de ehynd n'a pas empêché plusieurs requêtes identiques si celles-ci avaient été exécutées avant le retour de l'une d'entre elles. C'est,

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

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

Ceci peut être résolu en mettant en cache la demande différée à la place 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
Julian D.

Un différé peut être utilisé à la place d'un mutex. Ceci est essentiellement identique aux scénarios d’utilisation multiples ajax.

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

Lorsque vous utilisez un mode différé uniquement en tant que mutex, faites attention aux conséquences sur les performances (http://jsperf.com/deferred-vs-mutex/2). Bien que la commodité, ainsi que les avantages supplémentaires fournis par un différé, en valent la peine, et que, dans le cas d'une utilisation réelle (en fonction d'événements déterminés par l'utilisateur), l'impact sur les performances ne devrait pas être perceptible.

44
user406905

C’est une réponse auto-promotionnelle, mais j’ai passé quelques mois à effectuer des recherches et à présenter les résultats à la 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
Alex Mcp

Une autre utilisation que je mets à profit est de récupérer des données à partir de plusieurs sources. Dans l'exemple ci-dessous, je récupère plusieurs objets de schéma JSON indépendants utilisés dans une application existante pour les valider entre un client et un serveur REST. Dans ce cas, je ne souhaite pas que l'application côté navigateur commence à charger les données avant que tous les schémas ne soient chargés. $ .when.apply (). then () est parfait pour cela. Merci à Raynos pour les pointeurs sur l’utilisation ensuite (fn1, fn2) de la surveillance des 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
Elf Sternberg

Un autre exemple utilisant Deferreds pour implémenter un cache pour tout type de calcul (généralement des tâches à hautes performances ou longues):

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 des 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 this jsFiddle .

10
Julian D.

1) Utilisez-le pour assurer une exécution ordonnée des rappels:

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) Utilisez-le pour vérifier le statut de l'application:

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

jQuery.when(loggedIn, databaseReady).then(function() {
  //do something
});
9
Kernel James

Vous pouvez utiliser un objet différé pour créer une conception fluide qui fonctionne bien dans les navigateurs Webkit. Les navigateurs Webkit déclenchent un événement de redimensionnement pour chaque pixel de la fenêtre, contrairement à FF et IE qui déclenchent l'événement une seule fois pour chaque redimensionnement. Par conséquent, vous n’avez aucun contrôle sur l’ordre dans lequel les fonctions liées à votre événement de redimensionnement de fenêtre seront exécutées. 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 afin qu'il s'exécute comme vous le souhaitiez. Méfiez-vous des pièges lors du passage de méthodes d'objet en tant que rappels à un objet différé. Une fois que cette méthode est exécutée en tant que rappel du différé, la référence "ceci" sera remplacée par la référence à l'objet différé et ne fera plus référence à l'objet auquel la méthode appartient.

2
Miloš Rašić

Vous pouvez également l'intégrer à toutes les bibliothèques tierces utilisant JQuery.

Une de ces bibliothèques est Backbone, qui supportera en fait Deferred dans leur prochaine version. J'en ai parlé aussi sur mon blog

2
Diego

Je viens d'utiliser différé dans le code réel. Dans le projet jQuery Terminal J'ai la fonction exec qui appelle les commandes définies par l'utilisateur (comme s'il la saisissait et appuyait sur entrée), j'ai ajouté Deferreds à l'API et appeler exec avec des tableaux. comme ça:

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 du code asynchrone, et exec doit appeler un code d'utilisateur dans l'ordre. Ma première API utilise une paire d’appels en pause/reprise et, dans la nouvelle API, j’appelle celles-ci automatiquement lorsque l’utilisateur retourne sa promesse. Donc, le code d'utilisateur peut simplement 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 un code comme celui-ci:

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 de reprise qui appelle à nouveau exec avec toutes les commandes dalyed.

et une partie de la fonction des commandes (j'ai dépouillé les parties non liées)

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
jcubic

La réponse par ehynds ne fonctionnera pas, car elle met en cache les données de réponses. Il devrait mettre en cache le jqXHR qui est aussi une promesse. Voici le bon code:

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 constitue une meilleure solution.

1
John Berg