Self-Indulgent Code: Asynchronous Process Manager for JavaScript

Self-Indulgent Code: Asynchronous Process Manager for JavaScript
http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/digg_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/reddit_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/dzone_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/stumbleupon_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/delicious_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/blogmarks_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/newsvine_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/google_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/myspace_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/facebook_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/yahoobuzz_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/sphinn_16.png http://www.nurelm.com/themanual/wp-content/plugins/sociofluid/images/twitter_16.png

Lately, I have been building sites which in that AJAX way gather large amounts of user information from a single page site. In the case of an application like this it is essential that all the site data be either accessed directly when needed from the HTML element which houses it or some other kind of tracking object. I prefer the latter as it makes the data easier to normalize and manipulate. This means that event handlers must be setup on all input fields to track click and change events. Of course this isn’t very hard to accomplish but it feels like a lot of code replication and it spreads out the code which updates the tracking objects. The former merely annoys me and the latter wastes time during testing. My solution is to move away from event handlers and aggregate data on a timed basis. I cannot help the fact that there will still be some code repetition but I don’t have to worry about how browsers interpret event handlers (IE). Below is the code for a class to aide in just such a goal.

The initial hurdle was how to mock multi-threading in JS. Unlike a language like Java there is no concurrency in JS but we can time processes and execute them asynchronously. In short there is still an execution order but these pseudo threads can be scheduled around each other. http://articles.sitepoint.com/article/multi-threading-javascript explains in more detail the topic of JS pseudo threading and acted as the core of how this process manager creates non colliding threads. If we are solely interested in creating a pseudo thread to execute a process once we can leverage this technique simply using setInterval(). http://www.elated.com/articles/javascript-timers-with-settimeout-and-setinterval/ gives a great overview of all the JS timing functions.

var processor = setInterval(function() {
  if (!busy) {  //this assures that if the function takes longer then the interval it will only execute once
    busy = true; //process executing
    func.apply(null, [1,2]); //executes the function with externally assigned parameters
    //the above line can also be replaced with any other function()
    clearInterval(processor); //ends the async interval so it can only execute once
    busy = false;
  }
}, 100);

The above code can be used anytime you want an asynchronous process. Let us cover the implications of an asynchronous process. When a long process executes in JS the browser can lock and making it difficult to have an interactive environment for users. An asynchronous process does not execute in sequence with the rest of your code, like a thread branch. The downside of an asynchronous process is that if you attempt to get a return value from it you will receive a null or undefined value. This makes sense when you consider that the code is executing outside of the code around it so the return comes before the execution. Asynchronous processes rely on a callback function to set the return into a static context within its scope. That said, we can see that this type of execution is not the best for all cases, like when you need to process the data now. It can be hard to mix synchronous and asynchronous processes in the same app when they return values for this reason.

There are a large number of methods included in this class but the general implementation is very simple.

//this function will display the internal process list to a div element called ProcessList or any element of your choice
function ProcessListDisplay(Body) {
  document.getElementById("ProcessList").innerHTML = Body;
}
AsyncProcessManager = new AsyncSequencer();
//this displays the process list
//The first argument is the function above
//The second is a shorthand array with one element. This displays how to pass a function as an argument so ProcessListDisplay can get an updated version of the process list on each interval
//The third argument is the timing interval
AsyncProcessManager.enQueueStart(ProcessListDisplay, [ function() {
  return AsyncProcessManager.getActiveProcessList();
} ], 2000);
//finally is an example of how robust this manager can be at thread handling
//this will continue to recursively create processes which will create more processes
//this will continue until the browser freaks out something like 1.2 million processes
//in the meantime you can still interact with the page
AsyncProcessManager.enQueueProcess(recursiveProcessCreation, [], 1000);
function recursiveProcessCreation() {
  AsyncProcessManager.enQueueProcess(recursiveProcessCreation, [], 900);
  AsyncProcessManager.spawnProcesses();
}

Finally here is the whole code. There is a link at the bottom for a download.

/**
 * @author Paul Scarrone (NuRelm)
 * This is the implementation of a process scheduler/manager for creating
 * and tracking asyncronous JavaScript Processes. Multiple process managers can be defined
 * but they will share a global PID object and no matter how many managers there are PIDs
 * will not collide
 */

/**
 * Id manager for generating unique ids
 */
function IDManager() {
  this.IDs = new Array();
  /**
   * Finds the last used ID and then increments and tracks that id
   * @return Integer the next fresh id is returned
   */
  this.nextID = function() {
    if (this.IDs.length == 0) {
      this.IDs.push(1);
    } else {
      this.IDs.push(this.lastID() + 1);
    }
    return this.lastID();
  };
  /**
   * finds the last used ID
   * @return Integer/null the last used id or null
   */
  this.lastID = function() {
    if (this.IDs != 0) {
      var IDtemp = this.IDs.pop();
      this.IDs.push(IDtemp);
      return IDtemp;
    } else {
      return null;
    }
  };
}
/**
 * Globally unique ids will be provided for all objects of AsyncSequencer
 */
AsyncSequencer.prototype.IDManager = new IDManager();
/**
 * Public dynamic class to manage timed JS processes
 * @member getActiveProcessList
 */
function AsyncSequencer() {
  var pendingQueue = new Array();
  var manager;
  var activeProcessList = "";
  var allProcessList = "";
  var that = this;
  var QueueSort = function(a, b) {
    if (a.priority < b.priority)
      return -1;
    else if (a.priority == b.priority)
      return 0;
    else
      return 1;
  };
  this.getActiveProcessList = function() {
    return activeProcessList;
  };
  this.getAllProcessList = function() {
    return allProcessList;
  };
  /**
   * internal process object constructor with simple self validation
   * @constructor
   * @param Pid Integer
   * @param Func Function
   * @param Args Array
   * @param Interval Integer
   * @param Handler Integer
   */
  var processObj = function(Pid, Func, Args, Interval, Handler) {
    if (typeof (Pid) != "number")
      Pid = -1;
    if (typeof (Func) != "function")
      Func = function() {
      };
    if (typeof (Args) != "object")
      Args = [];
    if (typeof (Interval) != "number")
      Interval = 1000;
    if (typeof (Handler) == "undefined")
      Handler = "";
    this.pid = Pid;
    this.process = Func;
    this.args = Args;
    this.priority = Interval;
    this.processHandler = Handler;
  };
  this.getProcessQueue = function() {
    return pendingQueue;
  }
  /**
   * Places a process in the process queue but doesn't start it.
   * @param Func Function
   * @param Args Array
   * @param Interval Integer
   * @return Process Id of the new Process
   */
  this.enQueueProcess = function(Func, Args, Interval) {
    var startID = this.IDManager.nextID();
    pendingQueue.push(new processObj(startID, Func, Args,
      Interval));
    return startID;
  };
  /**
   * The Same as enQueueProcess except it starts the returned pid as well
   * @see enQueueProcess
   * @param Func
   * @param Args
   * @param Interval
   * @return Process Id of the newly started Process
   */
  this.enQueueStart = function(Func, Args, Interval) {
    var startID = this.IDManager.nextID();
    pendingQueue.push(new processObj(startID, Func, Args, Interval));
    this.startProcess(startID);
    return startID;
  };
  /**
   * Private method to clear any processes whose function property is null
   */
  var removeProcess = function() {
    var tempQueue = new Array();
    for ( var i in pendingQueue) {
      if (pendingQueue[i].process != null) {
        tempQueue.push(pendingQueue[i]);
      }
    }
    pendingQueue = tempQueue;
  };
  /**
   * Stops and removes the process associated with the PID
   * @param pid Integer
   * @return Boolean if the removal was successful
   */
  this.deQueueProcess = function(pid) {
    for ( var i in pendingQueue) {
      if (pid == pendingQueue[i].pid) {
        clearInterval(pendingQueue[i].processHandler);
        pendingQueue[i].processHandler = "";
        pendingQueue[i].process = null;
        removeProcess();
        return true;
      }
    }
    return false;
  };
  /**
   * Stops and removes all processes from the queue
   */
  this.deQueueAll = function() {
    for ( var i in pendingQueue) {
      clearInterval(pendingQueue[i].processHandler);
      pendingQueue[i].processHandler = "";
      pendingQueue[i].process = null;
    }
    removeProcess();
    internalLister();
  };
  /**
   * Stops a process specified by PID but doesn't remove it from the queue
   * @param pid Integer
   * @return Boolean If the process was found and stopped returns true
   */
  this.killProcess = function(pid) {
    for ( var i in pendingQueue) {
      if (pid == pendingQueue[i].pid) {
        clearInterval(pendingQueue[i].processHandler);
        pendingQueue[i].processHandler = "";
        return true;
      }
    }
    return false;
  };
  /**
   * Stops all active processes removes their processHandler but leaves then Queued
   */
  this.killAll = function() {
    for ( var i in pendingQueue) {
      if(pendingQueue[i].processHander != ""){
        clearInterval(pendingQueue[i].processHandler);
        pendingQueue[i].processHandler = "";
      }
    }
  };
  /**
   * Start a queued process that is currently not running
   * @param pid Integer
   * @return Boolean if the process is found and started returns true
   */
  this.startProcess = function(pid) {
    for ( var i in pendingQueue) {
      if (pid == pendingQueue[i].pid
        && pendingQueue[i].processHandler == "") {
        pendingQueue[i].processHandler = asyncExecute(
          pendingQueue[i].process, pendingQueue[i].args,
          pendingQueue[i].priority);
        return true;
      }
    }
    return false;
  };
  /**
   * Starts all Queued non running processes
   */
  this.spawnProcesses = function() {
    for ( var i in pendingQueue) {
      if (pendingQueue[i].processHandler == "") {
        pendingQueue[i].processHandler = asyncExecute(
          pendingQueue[i].process, pendingQueue[i].args,
          pendingQueue[i].priority);
      }
    }
  };
  /**
   * Private object representing the Pid and Handler ID of a process
   * @deprecated
   * @constructor
   * @param Pid Integer
   * @param Handler Integer
   */
  var handlerObj = function(Pid, Handler) {
    this.pid = Pid;
    this.handler = Handler;
  };
  /**
   * Gets the process handler object of one or more processes by function name
   * @param funcName
   * @return Array of handlerObj if there are more then one process by the same name
   * @return handlerObj if there is only one process that matches that name
   */
  this.getProcessHandler = function(funcName) {
    var handlers = new Array();
    for ( var i in pendingQueue) {
      if (funcName == pendingQueue[i].process.prototype.constructor.name) {
        handlers.push(new handlerObj(pendingQueue[i].pid,
          pendingQueue[i].processHandler));
      }
    }
    if (handlers.length > 1) {
      return handlers;
    } else {
      return handlers[0];
    }
  };
  /**
   * Stops all processes of a particular function name
   * @param funcName
   * @return Boolean if the function found and stopped process/processes returns true
   */
  this.stopSpawnedProcess = function(funcName) {
    var somethingDeleted = false;
    for ( var i in pendingQueue) {
      if (funcName == pendingQueue[i].process.prototype.constructor.name) {
        clearInterval(pendingQueue[i].processHandler);
        pendingQueue[i].processHandler = "";
        somethingDeleted = true;
      }
    }
    return somethingDeleted;
  };
  /**
   * Starts all processes under a single time manager
   * @deprecated
   * @param interval
   * @return
   */
  this.runOnInterval = function(interval) {
    if (typeof (interval) == "undefined") {
      interval = 1000;
    }
    manager = setInterval(function() {
      pendingQueue.sort(QueueSort);
      for ( var i in pendingQueue) {
        pendingQueue[i].processHandler = asyncExecute(
          pendingQueue[i].process, pendingQueue[i].args);
      // pendingQueue[i].process = null;
      }
    }, interval);
  };
  /**
   * Stops all processes started with runOnInterval
   * @deprecated
   */
  this.stopAll = function() {
    clearInterval(manager);
  };
  /**
   * Private function which executes a given function and its parameters on a unique timer interval
   * This is the core functionality of the process manager
   * arguments that need to update with every execution need to be passed as callback arguments
   * any callback arguments will be executed at each interval
   * @param func Function the process function
   * @param args Array the array of static parameters or dynamic callback functions
   * @param interval Integer Interval time in ms
   * @return Integer the processor that is associated with this interval
   */
  var asyncExecute = function(func, args, interval) {
    var busy = false;
    if (typeof (interval) == "undefined") {
      interval = 100;
      var processor = setInterval(function() {
        var exeArgs = new Array();
        for ( var j in args) {
          if (typeof (args[j]) == "function") {
            exeArgs.push(args[j]());
          } else {
            exeArgs.push(args[j]);
          }
        }
        if (!busy) {
          busy = true;
          func.apply(null, exeArgs);
          clearInterval(processor);
          busy = false;
        }
      }, interval);
    } else {
      var processor = setInterval(function() {
        var exeArgs = new Array();
        for ( var j in args) {
          if (typeof (args[j]) == "function") {
            exeArgs.push(args[j]());
          } else {
            exeArgs.push(args[j]);
          }
        }
        if (!busy) {
          busy = true;
          func.apply(null, exeArgs);
          busy = false;
        }
      }, interval);
    }
    return processor;
  };
  /**
   * Builtin Process which keeps track of some of the queue data in a outputable list for convience
   */
  var internalLister = function() {
    that.enQueueStart(function ProcessLister() {
      var list = that.getProcessQueue();
      var listbody = "";
      var fullListBody = "";
      for ( var i in list) {
        if (list[i].processHandler != "") {
          listbody += list[i].pid + " "
          + list[i].process.prototype.constructor.name + " "
          + list[i].priority + "
\n"; } fullListBody += list[i].pid + " " + list[i].process.prototype.constructor.name + " " + list[i].priority + "
\n"; } activeProcessList = listbody; allProcessList = fullListBody; }, [], 2000); } internalLister(); }


asyncProcessManager Download

About the Author

Software Engineer and Code Enthusiast. I enjoy British comedies and long walks on the beach when I am not exclaiming, "Nope, you didn't write it general enough", and "For you, Object Oriented Programming was something that happened to other people". On a serious note, I will always remain to be a student of programming. At least until the day I transform into a creature with the middle name Mathison, and the initials AT.

Author Profile: