admin管理员组文章数量:1357724
It's a common pattern to implement timeout of some asynchronous function, using 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();
}
Now we can execute some asynchronous function called myFunc
and handle timeout:
// 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' );
}
);
OK, let's make a twist in this story! Imagine that the myFunc
itself returns a promise (NOTE: promise NOT deferred and I can't change it):
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();
}
Now if I wrap myFunc
in timeout
, I will loose the ability to handle errors different then timeout. If myFunc
emit progress events, I will loose this as well.
So the question is: how to modify timeout
function so it can accept functions returning promises without loosing their errors/progress information?
It's a common pattern to implement timeout of some asynchronous function, using 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();
}
Now we can execute some asynchronous function called myFunc
and handle timeout:
// 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' );
}
);
OK, let's make a twist in this story! Imagine that the myFunc
itself returns a promise (NOTE: promise NOT deferred and I can't change it):
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();
}
Now if I wrap myFunc
in timeout
, I will loose the ability to handle errors different then timeout. If myFunc
emit progress events, I will loose this as well.
So the question is: how to modify timeout
function so it can accept functions returning promises without loosing their errors/progress information?
3 Answers
Reset to default 9function 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();
}
I realize this is 2 years old, but in case someone is looking for the answer...
I think Benjamin was close in that you'll want your timeout to be handled separately, so we'll start with his delay function.
function delay(ms){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); }, ms);
return d.promise();
}
Then, if you wanted to wait before code is executed you can call the method you want delayed as a result of this 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));
});
}
This is usually what I'm trying to do when I go looking for a refresher (why I'm here). However, the question was not about delaying the execution, but throwing an error if it took too long. In that case, this complicates things because you don't want to wait around for the timeout if you don't have to, so you can't just wrap the two promises in a "when". Looks like we need another deferred in the mix. (See 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;
});
}
We can streamline this, knowing that in this case the master defer only rejects if the timeout happens first and only resolves if the functPromise resolves first. Because of this, we don't need to pass the functPromise to the master defer resolve, because it's the only thing that could be passed and we're still in scope.
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;
});
}
You should always promsiify at the lowest level possible. Let's start from the basics.
I'll use jQuery promises here, but this should really be done with a stronger library like Bluebird Let's start simple, by creating our delay
as:
function delay(ms){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); }, ms);
return d.promise();
}
Note delay doesn't do anything surprising, all our delay function does is cause a delay of ms
milliseconds.
Now, for your library, we want to create a version of doSomething
that works with promises:
superImportantLibrary.doSomethingAsync = function(){
var d = $.Deferred();
superImportantLibrary.doSomething(function(data){ d.resolve(data); });
return d.promise();
};
Note both our delay and doSomethingAsync
functions both do just one thing. Now the fun begins.
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.
});
Now in Bluebird, this whole code would have been:
superImportantLibrary.doSomethingAsync().timeout(1000).then(function(){
// complete and did not time out.
});
本文标签: jqueryIn JavaScripthow to wrap a promise in timeoutStack Overflow
版权声明:本文标题:jquery - in JavaScript, how to wrap a promise in timeout? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738514242a2090989.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
superImportantLibrary.doSomething
if it's library and not my own code, can you write some example code to explain what do you mean by that? – mnowotka Commented Jun 12, 2014 at 14:58myFunc
is a way to promisifysuperImportantLibrary.doSomething
method. And it returns only a promise. Why do you say it's wrong? I would still appreciate some code explaining how would you do this in the correct way. Thanks for other links! – mnowotka Commented Jun 12, 2014 at 15:13