在JavaScript中,如何在超时中包装承诺?
使用deffered/promise实现某些异步函数的超时是一种常见模式:在JavaScript中,如何在超时中包装承诺?,javascript,jquery,promise,jquery-deferred,deferred,Javascript,Jquery,Promise,Jquery Deferred,Deferred,使用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
// 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
本身返回一个承诺(注意:承诺不延迟,我不能更改):
函数myFunc(){
var dfd=newjquery.Deffered();
superImportantLibrary.doSomething(函数(数据)){
如果(数据长度<5){
拒绝(数据太少);
}
否则{
dfd.决心(“成功!”);
}
},{'error_callback':函数(){
拒绝(“出现了问题,但不是超时”);}
}});
返回dfd.promise();
}
现在,如果我将myFunc
包装在timeout
中,我将失去处理与超时不同的错误的能力。如果myFunc
发出进度事件,我也会释放它
所以问题是:如何修改
timeout
函数,使其能够接受返回承诺的函数,而不丢失其错误/进度信息?您应该始终以尽可能低的级别提示。让我们从基础开始
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();
}
我将在这里使用jQuery Promissions,但这确实应该通过一个更强大的库(如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();
};
请注意,我们的delay和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岁,但万一有人在寻找答案 我认为Benjamin很接近于你希望你的超时被单独处理,所以我们从他的延迟函数开始
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));
});
}
这通常是我在寻找复习材料时要做的事情(为什么我会在这里)。然而,问题不是推迟执行,而是如果执行时间过长,就会抛出错误。在这种情况下,这会使事情变得复杂,因为如果不需要的话,你不想等待超时,所以你不能把这两个承诺用“何时”来包装。看来我们还需要一个延期的。(见附件)
我们可以简化这一过程,知道在这种情况下,只有在超时先发生时,主延迟才会拒绝,只有在functPromise先解决时,主延迟才会解决。因此,我们不需要将functPromise传递给master defer resolve,因为它是唯一可以传递的东西,并且我们仍然在范围内
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;
});
}
不。您不能对返回承诺的函数调用
reject
或resolve
。很抱歉,我不能将myFunc
更改为返回deferred而不是promise…我认为如果(deferred.state()==='pending')延迟,您可以跳过,因为deferred.reject()
如果延迟已被拒绝或解决,则不会执行任何操作。@JoeBrockhaus-嗯,审阅者只是错了(这种情况时有发生)。删除if(deferred.state()===“pending”)
只会删除死代码,不会以任何方式更改功能,因为deferred.reject()
在内部已经有自己的检查,如果状态不是pending
,则不会执行任何操作(根据承诺规范)。哦,好的。谢谢大家,我已经修好了。调用$.Deferred()
时是否需要新建
?我没有这么做。您的原语是错误的,您需要分两个阶段进行承诺,首先-承诺superImportantLibrary.doSomething方法,然后执行承诺返回。另外,请避免jQuery承诺,它们与其他实现相比是可怕的。@BenjaminGruenbaum-哪种实现?为什么jQuery承诺很可怕?你说“你的原语错了”是什么意思?我怎样才能“promisify”superImportantLibrary.doSomething
如果它是库而不是我自己的代码,你能写一些示例代码来解释你的意思吗?我担心我不需要证明自己就无法做出这样的声明:)所以(转换库本身),至于库,我会使用@BenjaminGruenbaum-somyFunc
是一种展示superImportantLibrary.doSomething
方法的方法。它只会回报一个承诺。你为什么说这是错的?我仍然希望有一些代码能够解释如何以正确的方式执行此操作。谢谢其他链接!我已经添加了一个答案,如果您对任何事情不确定,请随时要求澄清。是的,但您也可以编写timeout(myFunc(),1000)。然后(…)
,所以我真的不明白您为什么坚持用doSomethingAsync
替换它。除此之外,在promise.then(…)
部分中,应该有处理错误的代码,也应该有处理进度的代码,但我知道您将其作为实现细节。如果promise
被拒绝怎么办?进度通知呢?我感觉到一个延迟的反模式:-)最干净的解决方案可能类似于Promise.race(Promise,rejectAfterDelay(ms))
@Bergi是的,最干净的解决方案肯定是我在Bluebird上说的最新解决方案。问题是jQuery没有.race
方法或任何类似的方法,因此实现它将非常难看。doSomethingAsync
只是包装库函数,而不是对长度等进行验证检查。记住,承诺是安全的。
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;
});
}
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;
});
}