Promises with timeouts

An example adding a flexible timeout method to promises

I love asynchronous programming using promises. Q is my favorite promise implementation library. An asynchronous function can return a promise, and I can setup success and fail callbacks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var q = require('q');
// promise-returning function
function foo() {
var defer = q.defer();
setTimeout(function () {
defer.resolve();
}, 1000);
return defer.promise;
}
foo()
.then(function () {
console.log('foo has been resolved!');
})
.fail(function (msg) {
console.log('foo has failed with value:', msg);
});

What if we want to automatically fail if foo takes too long? Q has method Q.promise.timeout that can reject a promise with a given message

1
2
3
4
5
foo()
.timeout('Out of time')
.fail(function (err) {
// err is an Error with message 'Out of time'
});

How do I detect if the promise timed out or really failed? I have to check inside the .fail callback, making the code more complex:

1
2
3
4
5
6
7
8
9
foo()
.timeout('Out of time')
.fail(function (err) {
if (err instanceof Error && err.message === 'Out of time') {
console.log('foo timed out');
} else {
console.log('foo failed');
}
});

It is very simple to extend or wrap the Q's promise object and extend the timeout method and allow a dedicated callback function on time out, not just an error message. I placed the code into this gist. The main feature: we are wrapping the Q.defer function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var _defer = q.defer;
q.defer = function () {
var d = _defer();
d.promise.timeout = function (ms, timedOutValue) {
setTimeout(function () {
if (typeof timedOutValue === 'function') {
timedOutValue(d);
} else {
d.reject(timedOutValue);
}
}, ms);
return d.promise;
};
return d;
};

Now we can run the dedicated callback on timed out promises, which gets the original deferred object

1
2
3
4
5
6
7
8
9
foo()
.timeout(500, function (defer) {
console.log('timed out callback');
defer.reject(); // 1
})
.fail(function (msg) {
if (msg)
console.log('failed with value:', msg);
});

limitations

  • If we reject the promise (line // 1) we need to make sure the .fail callback can tell the difference between the timed out and failed promise. For example, by rejecting the promise without arguments in timeout and assuming that every true failure would have arguments.
  • Each .then function returns a different promise object. We only add the timeout method to the original promise created in q.defer(), so if you want a time out, it has to be the first call on the promise.

Update 1

I extended AngularJs $q promises with timeout method, see this post

Update 2

Q library has added promise.timeout method that works just like I described above. Any long-running promise can be limited to a given period. If the promise is not resolved after the period expires, the promise is rejected. This allows to start a normal long running action using an ordinary method, but limit it from the outside.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// "normal" promise-returning function, knows nothing about the time limit
function longAction() {
var deferred = Q.defer();
console.long('started long-running promise');
setTimeout(function () {
console.long('resolving long-running promise with 42 after 1 second');
return deferred.resolve(42);
}, 1000);
return deferred.promise;
}
longAction()
.timeout(400, 'timed out')
.then(onOk, onError)
.done();

This produces the following output

at 10 ms: started long-running promise
at 415 ms: promise error with value [Error: timed out]
at 1011 ms: resolving long-running promise with 42 after 1 second

While there is a message at 1011 ms before the deferred.resolve(42); is called, the promise has already been fulfilled. Thus onOk or onError is not going to be called the second time.

Important notice that the application DOES NOT EXIT until all promises get resolved (after 1 second). This is the problem with promises - they keep running and there is no way to get them cancelled without access to the original deferred object.

Update 3

Whenever working with asynchronous JavaScript code I like to see a timestamp next to some of the log statements. I find printing just the current time verbose and less than useful. I am more interested in the elapsed time rather than the local time. Thus instead of just using console-stamp I prefer to create a dedicated method that shows elapsed time in milliseconds since the "start".

1
2
3
4
5
6
7
8
var started = +(new Date());
console.stamp = function stamp() {
var elapsed = +(new Date()) - started;
var messageArgs = ['at %d ms:', elapsed].concat(Array.prototype.slice.call(arguments, 0));
console.log.apply(console, messageArgs);
}
console.stamp('normal message', 'with multiple arguments');
// at 10ms: normal message with multiple arguments