javascript timeout promise function

It’s not a rare scenario where you need to invoke an asynchronous function that you expect to run in a reasonable period of time. If the function fails to return a value in the expected period of time then you should trigger an action such as dispelling to the user an error message, falling back to another backup function or just throwing a plain old good exception…

A Timeout that returns a promise

By creating a timeout function that returns a promise we are able to trigger a race between 2 functions using the Promise.race API function. The new promise will resolve as the timeout expires.

function timeoutResolver(ms){
    return new Promise((resolve, reject) => {
        setTimeout(function () {
            resolve(true);
        }, ms)
    }); 
}

The timeoutResolver will return a new promise that once it invoked triggers a timeout with the requested amount of time in milliseconds. Once the time has expired the promise will be resolved.

function timeoutResolver(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(true);
    }, ms);
  });
}
timeoutResolver(1000).then(() => console.log("timer has expired"));

// After at least one seconed: timer has expired will apeare in the console

Basically, this is a timer that has been promisify.

Wait…. does it makes sense to resolve the promise?

That’s a very good question. Basically, since our goal is to finish the task in a reasonable amount of time if we will not win the race – this is not the expected outcome – we always strive to win. Let’s flip the promise around and reject it as the time out expires, now we will be able to catch an exception and learn that things didn’t turn out as expected.

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      reject();
    }, ms);
  });
}
timeoutResolver(1000).catch((err) => console.log("timer has expired"));

// After at least one second: timer has expired will in the console

A-wait? Switching to async-await

We can make things much easier to read if we switch to the async-await syntax and refactor

async main(){
    try{
        await timeoutResolver(1000);
    }
    catch(err){
        console.log("timer has expired")
    }
  
}

Promise race

promise race timeout
Photo by Pixabay on Pexels.com

We would like to do is create a race between our asynchronous function and the “promisfy” timer hoping our function will win and resolved before the timeout promise will expire and rejects – causing an exception to be thrown. We can use the promise API for this with the Promise.race

const ourFunction = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const timerPromise = timeout(1000);

Promise.race([ourFunction, timerPromise]).then((value) => {
  console.log('we made it on time');
}).catch(()=>console.log('our function didnt finish on time'));

//. async-await version
async main(){
    try {
        await Promise.race([ourFunction, timerPromise]);
        console.log('we made it on time');
    }
    catch(err){
        console.log('our function didnt finish on time')
    }

}
main();

Play with “ourFunction” timeout parameter to simulate what happens in both cases and you will be able to see that the race is on!

A challenge – try to implement the promise.race yourself

If you got until here and it seems to easy, let’s try to implement the Promie.race function ourselves. start by defining the input and output of the function:

Parameters – iterableAn iterable object, such as an Array. See iterable.

Return value – A pending Promise that asynchronously yields the value of the first promise in the given iterable to fulfill or reject.

Turns out we only need to return the first value – not even an array of undefined except to the relevant promise at the correct index that won the race:

we will use the spread operator – this will be an input of an array, once we got the array we need to iterate over it and trigger to each promise with the then method and resolve the first one that finishes – we also need to return a new Promise to the caller of the function

function race(...promises){
      return new Promise((resolve, reject) => {
          promises.forEach(p=>p.then((val=>resolve(val))));
  });

}

The first promise that resolves will trigger the resolve function of the main function and the problem solved! nowhere comes a question of what happens if after the winner of the race has resolved and now the next in line resolved or worse – it rejects? In case of a resolve nothing but in case of a reject – seems like in node an unhandled rejection will cause node to crash in the feature.

(node:40401) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Better to just resolve this first one and keep a flag indicating that the promise has been resolved and just ignore the rest of the promises. Let’s refactor plus we will handle rejection if occurs

function race(...promises) {
  let hasRaceEnded = false;
  return new Promise((resolve, reject) => {
    promises.forEach((p) => {
      p.then((val) => {
        if (!hasRaceEnded) {
          hasRaceEnded = true;
          resolve(val);
        }
      });
      p.catch((err) => {
        if (!hasRaceEnded) {
          hasRaceEnded = true;
          reject(val);
        }
      });
    });
  });
}

race(timeoutResolver(1000), timeoutResolver(750)).then((val) => console.log("I won the race ", val));

and we are done!

keep learning javascript with us!

Timeout async functions in Javascript using a promise

Yoni Amishav


Tech lead, blogger, node js Angular and more ...


Post navigation


Leave a Reply

Free Email Updates
Get the latest content first.
We respect your privacy.
%d