16

How would you wait for a Promise to resolve/reject, for a maximum execution time ? The code below is obviously wrong, it's just to explain what I'm trying to achieve. I'm clueless.

await doSomething();
if ( executionTime > maxExecutionTime ) {
    doSomethingElse();
}

This is not for a bluebird promise.

standac
  • 1,027
  • 1
  • 11
  • 26
  • Possible duplicate of [How to set a time limit to run asynchronous function in node.js?](https://stackoverflow.com/questions/36587789/how-to-set-a-time-limit-to-run-asynchronous-function-in-node-js) – Eli Richardson Dec 08 '17 at 16:59
  • What kind of Promise do you have? If it's a bluebird Promise you can just do: `await doSomething().timeout(100);`. If it's not a bluebird Promise and you want to use bluebird, you can do this: `await Promise.resolve( doSomething() ).timeout(100);`, where `Promise` is the bluebird Promise constructor. – Paul Dec 08 '17 at 17:02

5 Answers5

24

You can use Promise.race() which will immediately resolve/reject when the first promise in its iterable resolves or rejects. E.g.

   
const longTask = () => new Promise(resolve =>
  setTimeout(() => resolve("Long task complete."), 300))

const timeout = (cb, interval) => () =>
  new Promise(resolve => setTimeout(() => cb(resolve), interval))

const onTimeout = timeout(resolve =>
  resolve("The 'maybeLongTask' ran too long!"), 200)

Promise.race([longTask, onTimeout].map(f => f())).then(console.log)

The only issue is you can't really cancel the 'longTask' just because of its long execution. In theory, you'd either set some flag (to tell it not to continue onto the next stage of its pipeline), or design your application with the consequences of the promise in mind.

See what happens when you swap the 200 and 300 intervals.

Edit: Per spsaucier's comment, I've delayed the execution of each promise until the Promise.line line.

noahnu
  • 3,479
  • 2
  • 18
  • 40
  • 1
    The way this is written, `onTimeout` is calling the function when it's declared, and it's not executing during the race. – Stephen Saucier Jun 21 '19 at 17:14
  • good point, I've updated the snippet to delay the execution until the race line at least – noahnu Jun 21 '19 at 20:53
  • The author asked for a way to limit the maximum execution time from the calling function and your answer is on how to do so from the recieving function. This approach only works if you are the owner of the code. What if I am awaiting a respose from an external API? – Nadav Jun 25 '21 at 10:12
  • @Nadav it's the same concept. I just provided an example promise. You can replace longTask with an external API, assuming that external API is a promise. – noahnu Jun 25 '21 at 12:08
11

The code below will give you some idea:

function doSomething(maxExecutionTime) {
    return new Promise(resolve => {
        setTimeout(() => resolve(true), 2000);  // this setTimeout simulates your async action which sould not exced maxExecutionTime
        setTimeout(() => resolve(false), maxExecutionTime);
    });
}

async function someFunc(maxExecutionTime) {
    var exced = await doSomething(maxExecutionTime);
    if (exced) {
        console.log("Doesn't exced max time");
    } else {
        console.log("Exced max time");
    }
}

someFunc(1000);
someFunc(3000);
Faly
  • 13,291
  • 2
  • 19
  • 37
0

In ECMA6 You can do something like this:

let promise = new Promise((resolve, reject) => {
  let wait = setTimeout(() => {
    clearTimeout(wait);
    resolve('Timed out after 200 ms..');
  }, 200)
})
Adrian
  • 8,271
  • 2
  • 26
  • 43
  • 1
    what clearTimeout does? can you show more details? How it help in setting time limit or execution time for a particular function? – junnyea Aug 21 '20 at 04:06
0

As noahnu suggested, you can use Promise.race. You can wrap it in a function that takes a promise.

With some syntax sugar you can use thisOrThat which takes logic, a function that takes 2 functions, first and second. You can apply logic here as to try the first function and when to try the second (in our case if the first doesn't resolve within a certain time then try the second).

thisOrThat then takes an argument first that is a function returning a promise (in our case doSomething.

thisOrThat returns an object that has an or property that takes a function returning a promise. That parameter is called second and is passed to logic as second (in our case it is doSomethingElse).

var timeoutPromise =
  time    =>
  promiseFn =>
    Promise.race([
      promiseFn(),
      new Promise(
        (_,r)=>
          setTimeout(
            _=>r("timed out")
            ,time
          )
        )
    ]);

var thisOrThat =
  logic     =>
  first  => ({
    or:second=>
      logic(first)(second)
  });
var within75ms = thisOrThat
  (first=>second=>
    timeoutPromise(75)(first)
    .catch(_=>second())
  );
var within25ms = thisOrThat
  (first=>second=>
    timeoutPromise(25)(first)
    .catch(_=>second())
  );

var doSomething = () =>
  console.log("do something called")||
  new Promise(r=>setTimeout(x=>r("something"),50));
var doSomethingElse = () =>
  console.log("do something else called") ||
  new Promise(r=>setTimeout(x=>r("something else"),50));


async function someFunc() {
  const doesNotTimeOut = 
    await within75ms(doSomething).or(doSomethingElse);
  console.log("within 75ms resolved to:",doesNotTimeOut);

  const doesTimeOut = 
    await within25ms(doSomething).or(doSomethingElse)
  console.log("within 25ms resolved to:",doesTimeOut);
};
someFunc();
HMR
  • 37,593
  • 24
  • 91
  • 160
0

I have used Promise.race

// Try to get config from mongo
async function getProjectConfigThreshold(projectName) {
    function onTimeoutResolveDefaultThreshold() {
        return new Promise(async (resolve) => {
            setTimeout(() => {
                resolve(DEFAULT_THRESHOLD);
            }, 2000)
        });
    }

    async function getThresholdFromProjectConfig() {
        const projectConfig = await getProjectsConfig(projectName);
        const threshold = projectConfig.threshold || DEFAULT_THRESHOLD;
        return threshold;
    }

    return await Promise.race([getThresholdFromProjectConfig(), onTimeoutResolveDefaultThreshold()]);
}
Itay Ben Shmuel
  • 648
  • 7
  • 9