Implementing scheduler's stop mechanism
Tags: javascript, scheduler, async
Published: 2019-11-21 19:44:23


In [previous post](https://dev.to/lvidakovic/improving-our-periodic-code-runner-832), we untangled our pause and loop mechanisms but the main flaw of not being able to stop the loop remained. Now we are going to rectify this. From previous code:

```javascript
const pause = time => new Promise(resolve => setTimeout(resolve, time));

async function* cycle(time) {
  while (true) {
    yield pause(time);
  }
}

async function runPeriodically(callback, time) {
  for await (let tick of cycle(time)) {
    await callback();
  }
}
```

And it's usage to log current time every 2 seconds:

```javascript
function logTime() {
  const time = new Date();
  console.log(time.toLocaleTimeString());
}

runPeriodically(logTime, 2000);

```

We'll need to transfer to a bit more complex constructs. We need to keep track of state to be able to gracefully stop. There are 2 approaches to encapsulate all the state and functionality: using some kind of factory function or a class. I'll stick with the factory function because I don't want to worry about *this*, *new*, what is public etc. Our code transforms into this:

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

  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() {
    for await (let tick of cycle(time)) {
      await callback();
    }
  }

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

  return {
    runPeriodically,
    stop
  };
}
```

*createScheduler* factory function expects object with 2 parameters, callback function that is executed at each cycle/iteration and time parameter that defines the duration of a cycle. The same parameters we previously sent to *runPeriodically* function. 2 local variables are used to gracefully stop the loop: **run** and **timeoutId**. When **run** switches to false, it's a signal to generator function to exit. Exit from the generator function in turn also causes our "for await" loop inside *runPeriodically* function to finish. Function that controls everything needed for a graceful stop is of course **stop**. It clears pending timeout(if any) and sets a flag to signal a termination of a generator's while loop. At the end of our factory function we return an object containing functions accessible from the outside code, a function to run the loop and a function to stop it.

Great! We now have a way to run and stop the loop whenever needed. But we have a new problem 😅. We probably want to handle only a single loop at any given time for a single scheduler instance. If someone called *runPeriodically* a couple of times in a row, this would start parallel but out-of-sync loops that share a common state used for termination. Not good! The cause of the issue is within a "for await" loop definition:

```javascript
for await (let tick of cycle(time)) {
  await callback();
}
``` 

`cycle(time)` creates a new iterator every time *runPeriodically* is called and the easiest way to fix this is to simply share this iterator through a common state. Just like with termination variables, and initialize a new one only if iterator is not yet created or the previous one has finished. To put it into code:


```javascript
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
  };
}
```

Main features are covered. We can:

- create an instance of a scheduler
- define a function we want to run on every cycle(callback) and a duration of a cycle
- run and stop the loop whenever we see fit
- additionally, *runPeriodically* resolves it's Promise when the loop is stopped which can be helpful

Example of usage:

```javascript
const scheduler = createScheduler({ callback: logTime, time: 2000 });
scheduler.runPeriodically();

// at any point in time we can halt the loop using
scheduler.stop()

// or restart it
scheduler.runPeriodically();
```

Pretty neat, but we can't know how long the **callback** function will execute. It's provided from somewhere outside and it's unsafe to wait for it indefinitely to finish. We need a timeout mechanism if we want to avoid our code hanging at some point because of an external function failing or taking long time to finish. Fortunately, this can be achieved in a pretty elegant way using Promise API and we'll see how in the next, shorter post 😅. Thanks for reading!