You can use Promise.race()
to clamp the execution time. It accepts multiple promises and returns a single one that will resolve as soon as the first of the promise arguments resolves:
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms));
//mock functions taking time
const func1 = () => sleep(100); // 0.100s
const func2 = () => sleep(2000); // 2s
const func3 = () => sleep(300); // 0.300s
//make a function that will clamp the wait time
const maxWait = ms => promise =>
Promise.race([sleep(ms), promise]);
async function main() {
const noMoreThan1Second = maxWait(1000);
console.time("func1");
await noMoreThan1Second(func1());
console.timeEnd("func1");
console.time("func2");
await noMoreThan1Second(func2());
console.timeEnd("func2");
console.time("func3");
await noMoreThan1Second(func3());
console.timeEnd("func3");
}
main();
Note that Promise.race()
will not cancel the task that the slower promise(s) are linked to. Since promises do not control asynchronous tasks, they are simply a notification mechanism. Therefore, using Promise.race()
simply means ignoring the results of the slower promises. The async task they do would still continue in the background and can still succeed or fail. If rejections need to be handled, then nothing changes with Promise.race()
:
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms));
async function func() {
console.log("func() started");
await sleep(2000);
console.log("func() finished");
}
async function main() {
console.log("calling func()");
await Promise.race([sleep(1000), func()]);
console.log("finished waiting for func()");
}
main();
As a more advanced usage, default values might be returned if a promise is not resolved in time:
//allow optional value to be delayed
const sleep = (ms, value) =>
new Promise(resolve => setTimeout(resolve, ms, value));
const maxWait = (ms, value) => promise =>
Promise.race([sleep(ms, value), promise]);
const func1 = () => sleep(100, "one");
const func2 = () => sleep(2000, "two");
async function main() {
const noMoreThan1Second = maxWait(1000, "default");
console.log(await noMoreThan1Second(func1()));
console.log(await noMoreThan1Second(func2()));
}
main();
Alternatively, there could be an error if something takes too long:
//allow optional value to be delayed
const sleep = (ms, value) =>
new Promise(resolve => setTimeout(resolve, ms, value));
const delayedReject = (ms, value) =>
new Promise((_, reject) => setTimeout(reject, ms, value));
const maxWait = (ms, value) => promise =>
Promise.race([delayedReject(ms, value), promise]);
const func1 = () => sleep(100, "one");
const func2 = () => sleep(2000, "two");
async function main() {
const noMoreThan1Second = maxWait(1000, "timeout");
try {
console.log(await noMoreThan1Second(func1()));
console.log(await noMoreThan1Second(func2()));
} catch(e) {
console.log("problem encountered:", e)
}
}
main();
As a small note, an alternative implementation of delayedReject()
that re-uses sleep()
would be:
const delayedReject = (ms, value) =>
sleep(ms)
.then(() => Promise.reject(value));
or shorter eta-reduced version:
const delayedReject = (ms, value) =>
sleep(ms, value)
.then(Promise.reject);