Home > Software design >  How does one terminate a counter process which runs in timed intervals?
How does one terminate a counter process which runs in timed intervals?

Time:09-17

I'm trying to create a program in JavaScript where the startTimer function calls the callback every interval milliseconds. The callback should then receive the counter parameter and return true if the timer continues operating or false if the timer stops operating. interval milliseconds after the timer is started, the callback should be called the first time. The first time callback is called, the counter should equal to 1, the second time equal to 2 and so on.

Right now, my startTimer function isn't working correctly and I'm not exactly sure where. The output should print the numbers 1,2,3,4,5 in 50ms intervals. Can anyone help me?

function startTimer(callback, interval) {
  var count = 0;
  var wait = setInterval(function() {
    count  ;
    if (count > callback) {
      clearInterval(wait);
    } else {
      console.log(count);
    }
  }, interval);
}

function callback(counter) {
  console.log(counter);
  return counter < 5;
}

// Expected: 1, 2, 3, 4, 5 with 50ms interval.
startTimer(callback, 50);

CodePudding user response:

You set the interval with the callback function, but you don't execute the function. That means, if you don't execute the function, the content of the function will be returned. Also, you wrote:

if (count > callback)

But callback is returned as either true or false. So then the if command is:

if (count > true/false)

So to fix the error, do:

function startTimer(callback, interval) {
  var count = 0;
  var wait = setInterval(function() {
    count  ;
    if (callback(count)) {
      clearInterval(wait);
    } else {
      console.log(count);
    }
  }, interval);
}

function callback(counter) {
  console.log(counter);
  return counter >= 5;
  //Also use >= 5 to check if the condition is 5 or greater than 5
  //if you write only > 5, then the interval will stop only when count is 6
}

// Expected: 1, 2, 3, 4, 5 with 50ms interval.
startTimer(callback, 50);

CodePudding user response:

I assume you forgot to pass the counter to the callback function (which validates the condition whether to print or to clear the interval)

function startTimer(callback, interval){
    var count = 0;
    var wait = setInterval(function() {
        count  ;
        if (!callback(count)){
            clearInterval(wait);
        }
        else {
            console.log(count);
        }
    }, interval);
}

function callback(counter) {
  return counter < 5;
}

// Expected: 1, 2, 3, 4, 5 with 50ms interval.
startTimer(callback, 50);

CodePudding user response:

  1. callback returns a boolean and so asking the code to test numbers against true or false will give you the incorrect output.

  2. You're not calling the callback function.

  3. If you want 1, 2, 3, 4, and 5 you need to adjust the callback function check.

All you need in that condition is callback(count) where you explicitly pass in the count, and get either true or false back in return, and switch how the conditional statements are called.

function startTimer(callback, interval) {
  let count = 0;
  let wait = setInterval(function() {
    count  ;

    // Call the callback with `count` as
    // an argument and if the
    // boolean it returns is true
    // log the number to the console
    if (callback(count)) {
      console.log(count);

    // Otherwise clear the interval
    } else {
      clearInterval(wait);
    }
  }, interval);
}

function callback(counter) {
  return counter <= 5;
}

// Expected: 1, 2, 3, 4, 5 with 50ms interval.
startTimer(callback, 50);

CodePudding user response:

Since the OP's custom callback already returns a boolean value from kind of a terminating constraint, one just, within the interval lambda, needs to adjust the clause for clearing the interval accordingly, which in addition does simplify the OP's original implementation ...

function startTimer(callback, interval) {
  let count = 0;
  let intervalId = setInterval(() => {
    if (!callback(  count)) {

      clearInterval(intervalId);
    }
  }, interval);
}

function callback(counter) {
  console.log(counter);
  return counter < 5;
}

// Expected: 1, 2, 3, 4, 5 with 50ms interval.
startTimer(callback, 50);

Edit

For use cases like the one of the OP or even more complex interval controlled tasks one could think about a generic clocked function which wraps functions that have to be time and/or condition based and repeatedly invoked into cyclically running (or better clocked) functions which e.g. can get actively terminated or even be provided with self controlling logic based on internally provided interval data.

The OP's already refactored example code from above then could be changed to something like this ...

// the OP's former custom callback
// turned into a clocked controller function.
function controller(data) {
  const { clock: { count }, terminate } = data;

  if (count > 5) {

    terminate();
  } else {
    console.log({ count });
  }
}
(() => {}).clocked(50, null, controller).call();

... which of cause has to be based for instance on a test balloon implementation like ... Function.prototype.clocked ...

function argsAndContextTest01() {
  function test(...argsArr) {
    console.log('argsArr, this : ', argsArr, this);
  }
  function logActiveState() {
    console.log('argsAndContextTest01 :: clockedTest.isActive() ? ', clockedTest.isActive())
  }
  const clockedTest = test.clocked(2000, { id: "test" });

  clockedTest("quick", "brown", "fox");

  setTimeout(() => clockedTest.terminate(), 5000);

  setTimeout(logActiveState, 500);
  setTimeout(logActiveState, 3000);
  setTimeout(logActiveState, 4500);
  setTimeout(logActiveState, 6000);
}

function argsAndContextTest02() {
  function contextualWelcome(name) {
    console.log(`${ this.greeting }, ${ name }.`);
  }
  function logActiveState() {
    console.log('argsAndContextTest02 :: clockedWelcome.isActive() ? ', clockedWelcome.isActive())
  }
  const clockedWelcome = contextualWelcome.clocked(1000);

  clockedWelcome.call({ greeting: 'Good morning' }, 'Mrs Smith');

  setTimeout(() => clockedWelcome.terminate(), 3500);

  setTimeout(logActiveState, 750);
  setTimeout(logActiveState, 1500);
  setTimeout(logActiveState, 2250);
  setTimeout(logActiveState, 3000);
  setTimeout(logActiveState, 3750);
}

function controllerTest() {
  function contextualWelcome(name) {
    console.log(`${ this.greeting }, ${ name }.`);
  }
  function logActiveState() {
    console.log('argsAndContextTest02 :: clockedWelcome.isActive() ? ', clockedWelcome.isActive())
  }

  function controller(data) {
    const { clock: {
      interval, startTime, timestamp, count
    }, target, args, proceed, terminate } = data;

    const passedTime = timestamp - startTime;
    const passedSeconds = passedTime / 1000;

    if (count > 5) {

      terminate();
    } else {
      console.log({
        interval,
        startTime,
        timestamp,
        passedSeconds,
        count,
        target,
        args,
        proceed,
      });
      proceed.apply(target, args);
    }
  }
  const clockedWelcome = contextualWelcome.clocked(500, { greeting: 'Good evening' }, controller);

  setTimeout(() => clockedWelcome.terminate(), 3500);
  setTimeout(() => clockedWelcome('Mr Snider'), 200);

  setTimeout(logActiveState, 100);
  setTimeout(logActiveState, 1100);
  setTimeout(logActiveState, 2100);
  setTimeout(logActiveState, 3100);
  setTimeout(logActiveState, 4100);
}

function originalProblemTest() {

  // the OP's former custom callback turned into a clocked controller function.
  function controller(data) {
    const { clock: { count }, terminate } = data;

    if (count > 5) {

      terminate();
    } else {
      console.log({ count });
    }
  }
  (() => {}).clocked(50, null, controller).call();
}
body { margin: 0; }
.as-console-wrapper { min-height: 87%; top: auto; }
<script>

(function (global, Reflect, Math, Number, Array, Function) {

  'use strict';

  const DEFAULT_INTERVAL = 200;

  const { isFinite, parseInt } = Number;
  const { setInterval, clearInterval } = global;

  function isFunction(value) {
    return (
      typeof value === 'function' &&
      typeof value.call === 'function' &&
      typeof value.apply === 'function'
    );
  }

  function getSanitizedTarget(value) {
    return value ?? null;
  }

  function getSanitizedInteger(value) {
    value = parseInt(value, 10);
    return isFinite(value) ? value : 0;
  }
  function getSanitizedPositiveInteger(value) {
    return Math.max(getSanitizedInteger(value), 0);
  }

  function createClockedFunction(interval, target, controller) {
    const proceed = this;

    let thisArg;
    let argsArr;

    let clockCount = null;
    let clockStart = null;

    let timerId = null;

    target = getSanitizedTarget(target);
    interval = getSanitizedPositiveInteger(interval) || DEFAULT_INTERVAL;

    function triggerController() {
      controller({
        clock: {
          interval,
          startTime: clockStart,
          timestamp: Date.now(),
          count:   clockCount,
        },
        target: thisArg,
        args: [...argsArr],
        proceed,
        terminate,
      });
    }
    function triggerProceed() {
      proceed.apply(thisArg, argsArr);
    }

    function terminate() {
      clearInterval(timerId);
      timerId = null;

      clockStart = null;
      clockCount = null;
    }

    function isActive() {
      return (timerId !== null);
    }

    function clocked(...argumentsArray) {
      thisArg = getSanitizedTarget(this) ?? target;
      argsArr = argumentsArray;

      if (isActive()) {
        terminate();
      }
      clockCount = 0;
      clockStart = Date.now();

      const trigger = isFunction(controller)
        ? triggerController
        : triggerProceed;

      timerId = setInterval(trigger, interval);
    }
    clocked.terminate = terminate;
    clocked.isActive = isActive;

    return (isFunction(proceed) && clocked) || proceed;
  }
  createClockedFunction.toString = () => 'clocked() { [native code] }';

  Reflect.defineProperty(Function.prototype, 'clocked', {
    configurable: true,
    writable: true,
    value: createClockedFunction,
  });

}((window || global || this), Reflect, Math, Number, Array, Function));

</script>

<button onclick="argsAndContextTest01()">1st args &amp; context test</button>
<button onclick="argsAndContextTest02()">2nd args &amp; context test</button>
<button onclick="controllerTest()">controller test</button>
<button onclick="originalProblemTest()">OP's test</button>
... <button onclick="console.clear()">clear console</button>

  • Related