1

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.

  • what are you trying to achieve exactly? because 1000ms is quite short response time for an async operation. If you want to run both requests at the same time you can use Promise.allSettled(), so they will run at the same time. – Deniz Karadağ Sep 21 '22 at 12:10
  • the fun2() opens a url when the user presses a button and the func1() stores a metadata for that click. So I don't want the user to have to wait for potentially 3-4secs until the API resolves. – Pradip Shrestha Sep 21 '22 at 12:13
  • https://stackoverflow.com/questions/21518381/proper-way-to-wait-for-one-function-to-finish-before-continuing – Robert Sep 21 '22 at 12:15
  • @PradipShrestha if they are not depending on each other, you can definitely use Promise.allSettled(). No need for complicated structure or loop here(in my opinion). – Deniz Karadağ Sep 21 '22 at 12:21

2 Answers2

3

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);
VLAZ
  • 26,331
  • 9
  • 49
  • 67
1

You can use Promise.race function for doing this. Here is an example

const func1 = new Promise(resolve => {
    setTimeout(() => {
        console.log("func1");
        resolve("func 1")
    }, 1200);
})

const func2 = () => {console.log("func2")};

const promiseMaximumWait = (promise, time) => {
    const controlPromise = new Promise((resolve) => {
        setTimeout(() => resolve("controlPromise"), time)
    });
    return Promise.race([controlPromise, promise]);
}


promiseMaximumWait(func1, 1000)
.then((val) => {
    console.log(val);
    func2();
})

From MDN Docs

The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.

So we have created a control promise which is a settimeout whose duration can be set by ourself and will act as the maximum time after which we have to move ahead. If the func1 resolves in that time, then we proceed with result from func1 and execute func2 immediately. If the maximum time has elapsed we take the result from controlPromise and execute func2.

criccode
  • 126
  • 6