Lets say i have two function i need to run
await func1()
await func2()
I want to wait for a maximum of 1000ms for func1() to complete. If it does not complete within the 1000ms then call func2(), don’t need to cancel the request for func1()
But if the func1() returns in less than 1000ms, let’s say 500ms, then it immediately executes the func2()
I thought of trying some custom sleep function but not sure on how I would exit the sleep before hand.
>Solution :
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);