在JavaScript中,如何在超时中包装一个promise?

这是使用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本身会返回一个promise(注意:promise not deferred,我无法更改):

 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发出进度事件,我也会松开它。

所以问题是:如何修改timeoutfunction,以便它可以接受返回promises的函数而不会丢失它们的错误/进度信息?

 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 promises,但这应该通过更强大的库来完成,比如Bluebird让我们开始简单,通过创建我们的delay

 function delay(ms){ var d = $.Deferred(); setTimeout(function(){ d.resolve(); }, ms); return d.promise(); } 

注意延迟没有做任何令人惊讶的事情,我们所有的延迟function都会导致ms毫秒的延迟。

现在,对于您的库,我们想要创建一个与promises一起使用的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岁,但万一有人正在寻找答案……

我认为Benjamin很接近你会希望你的超时分开处理,所以我们将从他的延迟function开始。

 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”中。 看起来我们需要在混合中推迟另一个。 (请参阅等待多个jQuery延迟中的第一个被解析? )

 function timeout(funct, args, time) { var d = $.Deferred(); // Call the potentially async funct and hold onto it's 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 it's 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; }); }