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