如何使用jQuery延迟?

jQuery 1.5带来了新的Deferred对象和附加方法.when.Deferred._Deferred

对于那些以前没有使用过.Deferred的人,我已经注释了来源

这些新方法的可能用途是什么,我们如何将它们纳入模式?

我已经读过API,所以我知道它是做什么的。我的问题是我们如何在日常代码中使用这些新特性?

我有一个简单的例子的缓冲区类,它按顺序调用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.when的演示和可能的使用。

能看到._Deferred的例子也很不错。

链接到新的jQuery.ajax源来获取例子是欺骗行为。

我特别感兴趣的是,当我们抽象出一个操作是同步完成还是异步完成时,可以使用哪些技术。

141143 次浏览

我能想到的最佳用例是缓存AJAX响应。下面是来自Rebecca murphy关于这个话题的介绍的修改示例:

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

基本上,如果值已经被请求过一次,然后立即从缓存中返回。否则,AJAX请求将获取数据并将其添加到缓存中。$.when/.then不关心这些;你所需要关心的是使用响应,在这两种情况下,响应都被传递给.then()处理程序。jQuery.when()将非promise /Deferred处理为Completed,立即执行链上的任何.done().then()

当任务可能异步操作,也可能不异步操作时,延迟非常适合,并且您希望从代码中抽象出该条件。

另一个使用$.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


});

我的另一个好的用途是从多个数据源获取数据。在下面的示例中,我将获取现有应用程序中使用的多个独立的JSON模式对象,以便在客户机和REST服务器之间进行验证。在本例中,我不希望浏览器端应用程序在加载所有模式之前就开始加载数据。$.when.apply().then()是完美的。感谢Raynos提供了使用then(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);
});

您可以使用延迟对象来实现在webkit浏览器中工作良好的流动设计。Webkit浏览器将为窗口调整大小的每个像素触发调整大小事件,不像FF和IE只对每次调整大小触发一次事件。因此,您无法控制绑定到窗口调整大小事件的函数的执行顺序。这样就解决了问题:

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

这将序列化代码的执行,以便它按照您的意图执行。在将对象方法作为回调传递给deferred对象时,要当心陷阱。一旦这样的方法作为deferred的回调执行,'this'引用将被覆盖为对deferred对象的引用,并且不再引用该方法所属的对象。

您还可以将它与任何使用JQuery的第三方库集成。

Backbone就是这样一个库,它将在下一个版本中支持Deferred。

可以用deferred来代替互斥量。这本质上与多个ajax使用场景相同。

互斥锁

var mutex = 2;


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


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


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

递延

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)。虽然Deferred提供的便利和额外的好处是值得的,但在实际(基于用户驱动的事件)使用中,性能影响不应该很明显。

这里是一个AJAX缓存的实现,与ehynd的回答略有不同。

正如爱丽丝的后续问题中所指出的,如果请求在其中一个返回之前执行,ehynd的实现实际上并不能阻止多个相同的请求。也就是说,

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

如果“xxx”的结果之前没有被缓存过,很可能会导致3个AJAX请求。

这可以通过缓存请求的deferred而不是结果来解决:

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

另一个使用Deferreds来为任何类型的计算(通常是一些性能密集型或长时间运行的任务)实现缓存的示例:

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

同样的底层缓存也可以用于缓存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);
});

你可以在这jsFiddle中使用上面的代码。

1)使用它来确保回调的有序执行:

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)用它来验证应用的状态:

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


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

这是一个自我宣传的答案,但我花了几个月的时间研究这个问题,并在2012年旧金山jQuery大会上展示了结果。

这是一个免费视频的谈话:

https://www.youtube.com/watch?v=juRtEEsHI9E

我只是在实际代码中使用了Deferred。在项目jQuery终端中,我有函数exec,调用由用户定义的命令(就像他输入并按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');
});

命令可以运行异步代码,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 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();
}
}
}
ehynds的答案将不起作用,因为它缓存了响应数据。它应该缓存jqXHR,这也是一个Promise。 下面是正确的代码:

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

Julian d的答案是正确的,是一个更好的解决方案。