DEV Community

Luka Vidaković
Luka Vidaković

Posted on

3 1

Timeout mechanism using Promise.race

So we have a scheduler factory function. Simple mechanism that runs code periodically. Here is the code:

function createScheduler({ callback, time }) {
  let run;
  let timeoutId;
  let ticker;

  function pause(time) {
    return new Promise(resolve => {
      timeoutId = setTimeout(resolve, time);
    });
  }

  async function* cycle(time) {
    run = true;

    while (run) {
      yield pause(time);
    }
  }

  async function runPeriodically() {
    if (!ticker || !run) {
      ticker = cycle(time);
    }

    for await (let tick of ticker) {
      await callback();
    }
  }

  function stop() {
    clearTimeout(timeoutId);
    run = false;
  }

  return {
    runPeriodically,
    stop
  };
}
Enter fullscreen mode Exit fullscreen mode

As said in the previous post, we can't blindly await on the callback function that is being passed from outside of our code. We can't know for sure how fast or even if it'll ever finish. To guard ourself from unexpected scenarios we'll implement a timeout mechanism using Promise.race function. It allows us to race with the response from the callback function. If callback isn't resolved until some amount of time we can force-skip into a next cycle. Code inside our "for await" loop will slightly change and incorporate our pause function which will act as a reference point in time before which we expect to see some results.

So instead of blindly awaiting on a callback:

await callback();
Enter fullscreen mode Exit fullscreen mode

This is how we would set a fixed 5 seconds timeout:

await Promise.race([callback(), pause(5000)]);
Enter fullscreen mode Exit fullscreen mode

This way we protect our piece of code from stalling when something goes bad within the logic of a callback function. To make it more ergonomic, let's parametrize the timeout duration and update our previous code:

function createScheduler({ callback, time, callbackTimeout }) {
  let run;
  let timeoutId;
  let ticker;

  function pause(time) {
    return new Promise(resolve => {
      timeoutId = setTimeout(resolve, time);
    });
  }

  async function* cycle(time) {
    run = true;

    while (run) {
      yield pause(time);
    }
  }

  async function runPeriodically() {
    if (!ticker || !run) {
      ticker = cycle(time);
    }

    for await (let tick of ticker) {
      if (callbackTimeout) {
        await Promise.race([callback(), pause(callbackTimeout)]);
      } else {
        await callback();
      }
    }
  }

  function stop() {
    clearTimeout(timeoutId);
    run = false;
  }

  return {
    runPeriodically,
    stop
  };
}
Enter fullscreen mode Exit fullscreen mode

It seems like we've covered potential pitfalls for regular use-cases, so next post will cover a bonus feature using async generators and will serve as a wrap for this series.

Top comments (0)

typescript

11 Tips That Make You a Better Typescript Programmer

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay