How can I timeout a promise after certain amount of time? I know Q has a promise timeout, but I'm using native NodeJS promises and they don't have .timeout function.
Am I missing one or its wrapped differently?
Alternatively, Is the below implementation good in means of not sucking up memory, actually working as expected?
Also can I make it somehow wrapped globally so I can use it for every promise I create, without having to repeat the setTimeout and clearTimeout code?
function run() {
logger.info('DoNothingController working on process id {0}...'.format(process.pid));
myPromise(4000)
.then(function() {
logger.info('Successful!');
})
.catch(function(error) {
logger.error('Failed! ' + error);
});
}
function myPromise(ms) {
return new Promise(function(resolve, reject) {
var hasValueReturned;
var promiseTimeout = setTimeout(function() {
if (!hasValueReturned) {
reject('Promise timed out after ' + ms + ' ms');
}
}, ms);
// Do something, for example for testing purposes
setTimeout(function() {
resolve();
clearTimeout(promiseTimeout);
}, ms - 2000);
});
}
Thanks!
Native JavaScript promises don't have any timeout mechanism.
The question about your implementation would probably be a better fit for http://codereview.stackexchange.com, but a couple of notes:
You don't provide a means of actually doing anything in the promise, and
There's no need for clearTimeout
within your setTimeout
callback, since setTimeout
schedules a one-off timer.
Since a promise can't be resolved/rejected once it's been resolved/rejected, you don't need that check.
So perhaps something along these lines:
function myPromise(ms, callback) {
return new Promise(function(resolve, reject) {
// Set up the real work
callback(resolve, reject);
// Set up the timeout
setTimeout(function() {
reject('Promise timed out after ' + ms + ' ms');
}, ms);
});
}
Used like this:
myPromise(2000, function(resolve, reject) {
// Real work is here
});
(Or you may want it to be a bit more complicated, see update under the line below.)
I'd be slightly concerned about the fact that the semantics are slightly different (no new
, whereas you do use new
with the Promise
constructor), so you might adjust that.
The other problem, of course, is that most of the time, you don't want to construct new promises, and so couldn't use the above. Most of the time, you have a promise already (the result of a previous then
call, etc.). But for situations where you're really constructing a new promise, you could use something like the above.
You can deal with the new
thing by subclassing Promise
:
class MyPromise extends Promise {
constructor(ms, callback) {
// We need to support being called with no milliseconds
// value, because the various Promise methods (`then` and
// such) correctly call the subclass constructor when
// building the new promises they return.
// This code to do it is ugly, could use some love, but it
// gives you the idea.
let haveTimeout = typeof ms === "number" && typeof callback === "function";
let init = haveTimeout ? callback : ms;
super((resolve, reject) => {
init(resolve, reject);
if (haveTimeout) {
setTimeout(() => {
reject("Timed out");
}, ms);
}
});
}
}
Usage:
let p = new MyPromise(300, function(resolve, reject) {
// ...
});
p.then(result => {
})
.catch(error => {
});
Live Example:
// Uses var instead of let and non-arrow functions to try to be
// compatible with browsers that aren't quite fully ES6 yet, but
// do have promises...
(function() {
"use strict";
class MyPromise extends Promise {
constructor(ms, callback) {
var haveTimeout = typeof ms === "number" && typeof callback === "function";
var init = haveTimeout ? callback : ms;
super(function(resolve, reject) {
init(resolve, reject);
if (haveTimeout) {
setTimeout(function() {
reject("Timed out");
}, ms);
}
});
}
}
var p = new MyPromise(100, function(resolve, reject) {
// We never resolve/reject, so we test the timeout
});
p.then(function(result) {
snippet.log("Resolved: " + result);
}).catch(function(reject) {
snippet.log("Rejected: " + reject);
});
})();
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
Both of those will call reject
when the timer expires even if the callback calls resolve
or reject
first. That's fine, a promise's settled state cannot be changed once it's set, and the spec defines calls to resolve
or reject
on a promise that's already settled as do-nothings that don't raise an error.
But if it bother you, you could wrap resolve
and reject
. Here's myPromise
done that way:
function myPromise(ms, callback) {
return new Promise(function(resolve, reject) {
// Set up the timeout
let timer = setTimeout(function() {
reject('Promise timed out after ' + ms + ' ms');
}, ms);
let cancelTimer = _ => {
if (timer) {
clearTimeout(timer);
timer = 0;
}
};
// Set up the real work
callback(
value => {
cancelTimer();
resolve(value);
},
error => {
cancelTimer();
reject(error);
}
);
});
}
You can spin that about 18 different ways, but the basic concept is that the resolve
and reject
we pass the promise executor we receive are wrappers that clear the timer.
But, that creates functions and extra function calls that you don't need. The spec is clear about what the resolving functions do when the promise is already resolved; they quit quite early.