Search code examples
javascriptfunctionsetintervalcancellationclearinterval

Javascript abort execution of a nested function


I have a function "main" called from several points of the program. The "main" calls another function "child" function. I would like to abort an execution of a "child" function if main function is called.

async function main() {
    await child()
}

async child function (){
   // doing something. periodically waiting for user's input.
   // if 'main' function is called in meantime. abort current excution.
}

Solution

  • From the above comments ...

    "It sounds like the OP is in need of an abstraction 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. One answer to How does one terminate a counter process which runs in timed intervals? provides both the implementation and a lot of running example code."

    ''Let's suppose invoking child triggers an interval of a child related process. Now the OP states ... "I would like to abort [the] execution of [the] child [interval] if main function is called". Let's suppose this can be achieved from within the main function. Wouldn't invoking child (as implemented with the current main function) not immediately trigger a new child related interval again?''

    Example code ...

    function childProcess(text) {
      console.log(text);
    }
    const clockedChildProcess = childProcess.clocked(1000);
    
    function main(text) {
      clockedChildProcess.terminate();
    
      clockedChildProcess(text);
    }
    
    function init() {
      document
        .forms[0]
        .addEventListener('reset', evt => {
    
          clockedChildProcess.terminate();
          console.clear();
        });
      document
        .forms[0]
        .addEventListener('submit', evt => {
          evt.preventDefault();
    
          main(evt
            .currentTarget
            .elements[1]
            .value
          );      
        });
    }
    init();
    body { margin: 0; }
    fieldset { margin: 0; padding: 0 3px 4px 3px; }
    fieldset [type="text"] { width: 50%; }
    .as-console-wrapper { min-height: 75%; 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>
    
    <form>
      <fieldset>
        <legend>log update</legend>
        <input type="text" value="Hallo World!" />
        <button type="submit">log</button>
        <button type="reset">terminate / reset</button>
      </fieldset>
    </form>