如何推迟使用jQuery?
jQuery 1.5带来了新的Deferred对象和附加的方法, .Deferred
, .Deferred
和._Deferred
。
对于那些没有使用.Deferred
在我注明它的源代码之前
这些新方法的可能用途是什么,我们如何去适应它们的模式?
我已经阅读了API和源代码 ,所以我知道它做了什么。 我的问题是如何在日常代码中使用这些新function?
我有一个缓冲区类的简单示例 ,按顺序调用AJAX请求。 (在前一个结束之后的下一个开始)。
/* 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(); }; };
我正在寻找.Deferred
和.Deferred
演示和可能的用途。
看到._Deferred
例子也是可爱的。
链接到新的jQuery.ajax
源代码的例子是作弊。
赏金:当我们抽象出一个操作是同步还是asynchronous完成的时候,告诉我们什么技术可用。
我能想到的最好的用例是cachingAJAX响应。 这里是Rebecca Murphey关于这个主题的介绍post的一个修改的例子:
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. });
基本上,如果在从caching立即返回值之前已经请求了该值。 否则,AJAX请求将获取数据并将其添加到caching中。 那么,那么这个$.when
并不关心这个; 所有你需要关心的是在这两种情况下使用传递给.then()
处理程序的响应。 jQuery.when()
处理非Promise / Deferred为Completed,立即在链上执行任何.done()
或.then()
。
当任务可以或不可以asynchronous操作时,延迟是完美的,并且你想从代码中抽象出这个条件。
使用$.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 });
这里是一个稍微不同的AJAXcaching实现,就像在ehynd的答案中一样 。
如fortuneRice的后续问题所述 ,如果请求是在其中一个请求返回之前执行的,那么ehynd的实现实际上并不能阻止多个相同的请求。 那是,
for (var i=0; i<3; i++) { getData("xxx"); }
如果“xxx”的结果之前还没有被caching,那么很可能会导致3个AJAX请求。
这可以通过caching请求的Deferreds而不是结果来解决:
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. });
延迟可以用来代替互斥。 这与多个Ajax使用场景基本相同。
MUTEX
var mutex = 2; setTimeout(function() { callback(); }, 800); setTimeout(function() { callback(); }, 500); function callback() { if (--mutex === 0) { //run code } }
DEFERRED
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 });
当仅使用Deferred作为互斥锁时,请注意性能影响(http://jsperf.com/deferred-vs-mutex/2)。; 尽pipeDeferred提供的便利以及额外的好处非常值得,但是在实际(基于用户驱动的事件)使用中,性能影响应该不明显。
这是一个自我宣传的答案,但我花了几个月时间研究这个问题,并在2012年旧金山jQuery会议上展示了结果。
这是一个免费的谈话video:
http://www.confreaks.com/videos/993-jqcon2012-i-promise-to-show-you-when-to-use-deferreds
另一个我一直在使用的用途是从多个来源获取数据。 在下面的示例中,我将获取用于现有应用程序中的多个独立JSON模式对象,以在客户端和REST服务器之间进行validation。 在这种情况下,我不希望浏览器端应用程序在载入所有模式之前开始加载数据。 $ .when.apply()。然后()是完美的。 感谢Raynos使用指针(fn1,fn2)来监视错误情况。
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); });
另一个例子使用Deferred
来实现任何types的计算caching(通常是一些性能密集或长时间运行的任务):
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("|"); };
下面是使用这个类来执行一些(模拟重)计算的例子:
// 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); });
相同的底层caching可以用来cachingAjax请求:
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); });
你可以在这个jsFiddle中玩上面的代码。
1)使用它来确保callback的有序执行:
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)用它来validation应用程序的状态:
var loggedIn = logUserInNow(); //deferred var databaseReady = openDatabaseNow(); //deferred jQuery.when(loggedIn, databaseReady).then(function() { //do something });
您可以使用延迟对象来制作一个在webkit浏览器中运行良好的stream畅devise。 Webkit浏览器将触发调整窗口大小的每个像素的resize事件,不像FF和IE,每个resize只触发一次事件。 因此,您无法控制绑定到窗口大小调整事件的函数的执行顺序。 像这样的东西解决了这个问题:
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); });
这将序列化您的代码的执行,以便它按照您的意图执行。 将对象方法作为callback传递给延迟的时候,要小心隐患。 一旦这样的方法被执行作为一个callback延迟,这个“引用”将被覆盖引用延迟的对象,将不再引用该方法所属的对象。
您也可以将它与任何使用JQuery的第三方库进行整合。
一个这样的库是Backbone,实际上它将在下一个版本中支持Deferred。 我也在我的博客上讨论过这个问题
我刚刚在实际代码中使用了Deferred。 在项目jQueryterminal我有函数执行调用命令定义的用户(就像他正在进入并按下Enter键),我已经添加了延期到API和数组调用exec。 喜欢这个:
terminal.exec('command').then(function() { terminal.echo('command finished'); });
要么
terminal.exec(['command 1', 'command 2', 'command 3']).then(function() { terminal.echo('all commands finished'); });
该命令可以运行asynchronous代码,exec需要按顺序调用用户代码。 我的第一个API使用一对暂停/恢复调用,并在新的API我呼吁那些自动时,用户回复诺言。 所以用户代码可以使用
return $.get('/some/url');
要么
var d = new $.Deferred(); setTimeout(function() { d.resolve("Hello Deferred"); // resolve value will be echoed }, 500); return d.promise();
我使用这样的代码:
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用于恢复函数,该函数使用所有dalyed_commands再次调用exec。
和部分命令的function(我剥去了不相关的部分)
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(); } } }
由于它会caching响应数据,所以不会有效。 它应该cachingjqXHR这也是一个承诺。 这里是正确的代码:
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. });
朱利安D.的答案将正确工作,是一个更好的解决scheme。