Jak można używać jQuery deferred?
JQuery 1.5 przynosi nowy obiekt Deferred i dołączone metody .when
, .Deferred
oraz ._Deferred
.
Dla tych, którzy nie używali .Deferred
zanim podałem źródło to
Jakie są możliwe zastosowania tych nowych metod, jak je dopasować do wzorców?
Przeczytałem już API i źródło , więc wiem, co robi. Moje pytanie brzmi jak możemy wykorzystać te nowe funkcje w codzienny kod?
Mam prosty przykład klasy bufora, która w kolejności wywołuje żądanie AJAX. (Następny zaczyna się po poprzednim).
/* 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();
};
};
Szukam demonstracji i możliwych zastosowań .Deferred
i .when
.
Byłoby również miło zobaczyć przykłady ._Deferred
.
Linkowanie do nowego jQuery.ajax
źródłem przykładów jest oszustwo.
Pokaż nam, jakie techniki są dostępne, gdy usuniemy, czy operacja jest wykonywana synchronicznie lub asynchronicznie.
11 answers
Najlepszy przypadek użycia, jaki mogę wymyślić, to buforowanie odpowiedzi AJAX. Oto zmodyfikowany przykład z wstępu Rebecci Murphey na temat :
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.
});
Zasadniczo, jeśli wartość została już zażądana raz, zanim zostanie natychmiast zwrócona z pamięci podręcznej. W przeciwnym razie żądanie AJAX pobiera dane i dodaje je do pamięci podręcznej. Na $.when
/.then
wszystko, co musisz się martwić, to użycie odpowiedzi, która jest przekazywana do obsługi .then()
w obie sprawy. jQuery.when()
obsługuje nie-obietnicę / odroczenie jako zakończone, natychmiast wykonując dowolne .done()
lub .then()
w łańcuchu.
Deferreds są idealne, gdy zadanie może lub nie może działać asynchronicznie, i chcesz wyodrębnić ten warunek z kodu.
Inny przykład świata rzeczywistego z użyciem helpera $.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
});
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-28 15:44:30
Oto nieco inna implementacja pamięci podręcznej AJAX jak w odpowiedzi ehynda .
Jak wspomniano w kolejnym pytaniu fortuneRice, implementacja ehynda nie zapobiegła wielu identycznym żądaniom, jeśli zostały one wykonane przed zwróceniem jednego z nich. Czyli
for (var i=0; i<3; i++) {
getData("xxx");
}
Najprawdopodobniej wywoła 3 żądania AJAX, jeśli wynik dla " xxx " nie był wcześniej buforowany.
Można to rozwiązać poprzez buforowanie żądania Deferreds zamiast wyniku:
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.
});
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:34
Odroczony może być użyty zamiast mutex. Jest to zasadniczo takie samo jak wiele scenariuszy użycia ajax.
MUTEX
var mutex = 2;
setTimeout(function() {
callback();
}, 800);
setTimeout(function() {
callback();
}, 500);
function callback() {
if (--mutex === 0) {
//run code
}
}
Odroczony
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
});
Używając Deferred jako mutex tylko, uważaj na wpływ wydajności (http://jsperf.com/deferred-vs-mutex/2). choć wygoda, jak również dodatkowe korzyści dostarczane przez odroczony jest dobrze warto, i w rzeczywistym (user driven event based) wykorzystanie wpływ na wydajność nie powinny być zauważalne.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-23 18:44:25
Jest to odpowiedź autopromocyjna, ale spędziłem kilka miesięcy badając to i przedstawiłem wyniki na konferencji jQuery w San Francisco 2012.
Oto darmowy film z rozmowy:
Http://www.confreaks.com/videos/993-jqcon2012-i-promise-to-show-you-when-to-use-deferreds
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-19 21:09:11
Innym zastosowaniem, które stosuję w dobrym celu, jest pobieranie danych z wielu źródeł. W poniższym przykładzie pobieram wiele niezależnych obiektów schematu JSON używanych w istniejącej aplikacji do walidacji między Klientem a serwerem REST. W tym przypadku nie chcę, aby aplikacja po stronie przeglądarki zaczęła ładować dane, zanim załaduje wszystkie schematy. $.kiedy.apply ().wtedy() jest do tego idealna. Dziękujemy Raynos za wskazówki dotyczące korzystania z then (fn1, fn2) do monitorowania błędów warunki.
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);
});
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-01 12:09:25
Inny przykład użycia Deferred
s do implementacji pamięci podręcznej dla dowolnego rodzaju obliczeń (zazwyczaj niektórych zadań wymagających dużej wydajności lub długotrwałych):
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("|");
};
Oto przykład użycia tej klasy do wykonania niektórych (symulowanych ciężkich) obliczeń:
// 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);
});
Ta sama podstawowa pamięć podręczna może być używana do buforowania żądań 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);
});
Możesz grać z powyższym kodem w tym jsFiddle.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-22 14:14:41
1) Użyj go, aby zapewnić zlecone wykonanie wywołań zwrotnych:
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) Użyj go, aby zweryfikować status aplikacji:
var loggedIn = logUserInNow(); //deferred
var databaseReady = openDatabaseNow(); //deferred
jQuery.when(loggedIn, databaseReady).then(function() {
//do something
});
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-20 05:54:25
Możesz użyć obiektu odroczonego, aby stworzyć płynny projekt, który działa dobrze w przeglądarkach webkit. Przeglądarki Webkit odpalą zdarzenie zmiany rozmiaru dla każdego piksela, w przeciwieństwie do FF i IE, które odpali Zdarzenie tylko raz dla każdej zmiany rozmiaru. W rezultacie nie masz kontroli nad kolejnością wykonywania funkcji związanych z Twoim zdarzeniem zmiana rozmiaru okna. Coś takiego rozwiązuje problem:
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);
});
Spowoduje to serializację wykonania Twojego kodu tak, aby był on wykonywany zgodnie z twoim przeznaczeniem to do. Uważaj na pułapki podczas przekazywania metod obiektowych jako wywołań zwrotnych do odroczonego. Gdy taka metoda jest wykonywana jako wywołanie zwrotne do deferred, odniesienie 'this' zostanie nadpisane w odniesieniu do obiektu deferred i nie będzie już odnosić się do obiektu, do którego należy metoda.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-01 12:27:06
Można go również zintegrować z dowolnymi bibliotekami innych firm, które korzystają z JQuery.
Jedną z takich bibliotek jest Backbone, który w rzeczywistości będzie obsługiwał ich kolejną wersję. Opowiadałem o tym również na moim blogu
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-22 05:34:20
Właśnie użyłem Deferred w prawdziwym kodzie. W projekcie jQuery Terminal mam funkcję exec, która wywołuje polecenia zdefiniowane przez użytkownika (tak jakby go wprowadzał i naciskał enter), dodałem Deferreds do API i wywołałem exec z tablicami. tak:
terminal.exec('command').then(function() {
terminal.echo('command finished');
});
Lub
terminal.exec(['command 1', 'command 2', 'command 3']).then(function() {
terminal.echo('all commands finished');
});
Polecenia mogą uruchamiać kod asynchroniczny, a exec musi wywoływać kod użytkownika w kolejności. Mój pierwszy api używa pary wstrzymywania/wznawiania wywołań, a w nowym API wywołuję te automatyczne, gdy użytkownik zwraca obietnicę. Więc kod użytkownika może po prostu użycie
return $.get('/some/url');
Lub
var d = new $.Deferred();
setTimeout(function() {
d.resolve("Hello Deferred"); // resolve value will be echoed
}, 500);
return d.promise();
Używam takiego kodu:
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 jest używany w funkcji resume, która ponownie wywołuje exec ze wszystkimi dalyed_commands.
I część funkcji poleceń (rozebrałem Nie powiązane części)
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();
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-02 07:34:17
ODPOWIEDŹ ehyndsa nie będzie działać, ponieważ buforuje DANE odpowiedzi. Powinien buforować jqxhr, co jest również obietnicą. Oto prawidłowy kod:
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.
});
ODPOWIEDŹ Juliana D. będzie działać poprawnie i będzie lepszym rozwiązaniem.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-28 07:44:50