使用deffered / promise实现某些异步函数超时的常见模式:
// Create a Deferred and return its Promise
function timeout(funct, args, time) {
var dfd = new jQuery.Deferred();
// execute asynchronous code
funct.apply(null, args);
// When the asynchronous code is completed, resolve the Deferred:
dfd.resolve('success');
setTimeout(function() {
dfd.reject('sorry');
}, time);
return dfd.promise();
}
现在我们可以执行一些名为myFunc
的异步函数并处理超时:
// Attach a done and fail handler for the asyncEvent
$.when( timeout(myFunc, [some_args], 1000) ).then(
function(status) {
alert( status + ', things are going well' );
},
function(status) {
alert( status + ', you fail this time' );
}
);
好的,让我们在这个故事中扭转局面!想象一下,myFunc
本身会返回一个承诺(注意:承诺不会延期而且我无法改变它):
function myFunc(){
var dfd = new jQuery.Deffered();
superImportantLibrary.doSomething(function(data)){
if(data.length < 5){
dfd.reject('too few data');
}
else{
dfd.resolve('success!');
}
}, {'error_callback': function(){
dfd.reject("there was something wrong but it wasn't timeout");}
}});
return dfd.promise();
}
现在如果我将myFunc
包裹在timeout
中,我将失去处理不同于超时的错误的能力。如果myFunc
发出进度事件,我也会放弃它。
所以问题是:如何修改timeout
函数,以便它可以接受返回promises的函数而不会丢失它们的错误/进度信息?
答案 0 :(得分:8)
function timeout(funct, args, time) {
var deferred = new jQuery.Deferred(),
promise = funct.apply(null, args);
if (promise) {
$.when(promise)
.done(deferred.resolve)
.fail(deferred.reject)
.progress(deferred.notify);
}
setTimeout(function() {
deferred.reject();
}, time);
return deferred.promise();
}
答案 1 :(得分:3)
你应该始终在尽可能最低的水平上进行宣传。让我们从基础开始。
我将在这里使用jQuery promises,但这应该通过更强大的库来完成,比如Bluebird让我们开始简单,通过创建我们的delay
:
function delay(ms){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); }, ms);
return d.promise();
}
注意延迟没有任何意外,我们所有的延迟函数都会导致延迟ms
毫秒。
现在,对于您的图书馆,我们希望创建一个适用于承诺的doSomething
版本:
superImportantLibrary.doSomethingAsync = function(){
var d = $.Deferred();
superImportantLibrary.doSomething(function(data){ d.resolve(data); });
return d.promise();
};
请注意我们的延迟和doSomethingAsync
功能只做一件事。现在好玩的开始了。
function timeout(promise,ms){
var timeout = delay(ms); // your timeout
var d = $.Deferred();
timeout.then(function(){ d.reject(new Error("Timed Out")); });
promise.then(function(data){ d.resolve(data); });
return d.promise();
}
timeout(superImportantLibrary.doSomethingAsync(),1000).then(function(data){
// handle success of call
}, function(err){
// handle timeout or API failure.
});
现在在Bluebird中,整个代码应该是:
superImportantLibrary.doSomethingAsync().timeout(1000).then(function(){
// complete and did not time out.
});
答案 2 :(得分:3)
我意识到这是2岁,但万一有人正在寻找答案......
我认为本杰明很接近你会希望你的超时分开处理,所以我们将从他的延迟功能开始。
function delay(ms){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); }, ms);
return d.promise();
}
然后,如果你想在代码执行之前等待,你可以调用你想要延迟的方法作为这个承诺。
function timeout(funct, args, time) {
return delay(time).then(function(){
// Execute asynchronous code and return its promise
// instead of the delay promise. Using "when" should
// ensure it will work for synchronous functions as well.
return $.when(funct.apply(null, args));
});
}
这通常是我在寻找复习时想要做的事情(为什么我在这里)。然而,问题不在于延迟执行,而是在花费太长时间时抛出错误。在这种情况下,这会使事情变得复杂,因为如果你不需要,你不想等待超时,所以你不能将两个promises包装在“when”中。看起来我们需要在混合中推迟另一个。 (见Wait for the first of multiple jQuery Deferreds to be resolved?)
function timeout(funct, args, time) {
var d = $.Deferred();
// Call the potentially async funct and hold onto its promise.
var functPromise = $.when(funct.apply(null, args));
// pass the result of the funct to the master defer
functPromise.always(function(){
d.resolve(functPromise)
});
// reject the master defer if the timeout completes before
// the functPromise resolves it one way or another
delay(time).then(function(){
d.reject('timeout');
});
// To make sure the functPromise gets used if it finishes
// first, use "then" to return the original functPromise.
return d.then(function(result){
return result;
});
}
我们可以简化这一点,因为在这种情况下,主延迟只会在超时发生时拒绝,并且只有在functPromise首先解析时才会解决。因此,我们不需要将functPromise传递给主延迟解析,因为它是唯一可以传递的东西,我们仍然在范围内。
function timeout(funct, args, time) {
var d = $.Deferred();
// Call the potentially async funct and hold onto its promise.
var functPromise = $.when(funct.apply(null, args))
.always(d.resolve);
// reject the master defer if the timeout completes before
// the functPromise resolves it one way or another
delay(time).then(function(){
d.reject('timeout');
});
// To make sure the functPromise gets used if it finishes
// first, use "then" to return the original functPromise.
return d.then(function(){
return functPromise;
});
}