Supplementary - Event Marshaller
A JavaScript Library for Managing Race Conditions
J
avaScript functions are invoked by other functions, user actions, timers and incoming server-events. However, invocation may occur unpredictably, and in a sequence that is ill-suited to the application. Moreover, race conditions may arise when the effects of two or more events are mutually dependent.
Proprietary logic can be used to manage this, but the problem can be generalised, and this page presents a small library-function that creates 'event marshaller' objects to which event notifications can be posted. Event marshallers act as prioritised queues, such that 'actions' that correspond to posted-events are dispatched subsequently and in a specific order if desired.
See the Supplementary Section's Introduction and Overview page for terms of use, a summary and comparison of the libraries available from this site, and other general technical-information.
Note that EventMarshaller objects will not operate on Internet Explorer 5 and earlier because those versions of that browser do not support the push and pop methods of the JavaScript Array type.

 function CreateEventMarshaller (EventMax, ClientCallPoint)
    {
    function ThrowException (MsgStub, ClientCallPoint) { throw new Error ("Error in call to "
                                                                         + MsgStub
                                                                         + ". Client-code call point: "
                                                                         + ClientCallPoint); }

    function CheckEventMaxArg (Arg, MsgPrefix, ArgName, ClientCallPoint)
       {
       if (       Arg      ===  undefined) { ThrowException (MsgPrefix + " - "                       + ArgName + " is undefined",   ClientCallPoint); }
       if (       Arg      ===  null)      { ThrowException (MsgPrefix + " - "                       + ArgName + " is null",        ClientCallPoint); }
       if (typeof Arg      !== "number")   { ThrowException (MsgPrefix + " - "                       + ArgName + " is non-numeric", ClientCallPoint); }
       if (       Arg      <    1)         { ThrowException (MsgPrefix + " - value of "              + ArgName + " is less than 1", ClientCallPoint); }
       if (       Arg      ===  Infinity)  { ThrowException (MsgPrefix + " - value of "              + ArgName + " is Infinity",    ClientCallPoint); }
       if (      (Arg % 1) >    0)         { ThrowException (MsgPrefix + " - non-integer Value for " + ArgName,                     ClientCallPoint); }
       }

    CheckEventMaxArg (EventMax, "CreateEventMarshaller", "EventMax", ClientCallPoint);

    var Responses = [];

    return {                                                     // Opening brace is misplaced (in the layout format used here) deliberately
                                                                 // in order to counter the line-breaking defect in JavaScript
       GetEventTotal : function () { return Responses.length; },

       GetEventMax   : function () { return EventMax; },
       SetEventMax   : function (NewEventMax, ClientCallPoint)
          {
          CheckEventMaxArg (NewEventMax, "SetEventMax method of EventMarshaller object", "NewEventMax", ClientCallPoint);

          var OldEventMax = EventMax;

          EventMax = NewEventMax;

          if (EventMax <= Responses.length) { this.Dispatch (); }

          return OldEventMax;

          },

       // -- -----------------------------------------------------------

       PostEvent : function (ResponseFunc, FuncArgs, Priority, ClientCallPoint)
          {
          if (ResponseFunc          ===  undefined) { ThrowException ("PostEvent method of EventMarshaller object - ResponseFunc is undefined",                 ClientCallPoint); }
          if (ResponseFunc          ===  null)      { ThrowException ("PostEvent method of EventMarshaller object - ResponseFunc is null",                      ClientCallPoint); }
          if (typeof ResponseFunc   !== "function") { ThrowException ("PostEvent method of EventMarshaller object - ResponseFunc does not refer to a function", ClientCallPoint); }

          function CheckPriorityArg (Arg, FuncName, ObjName, ArgName, ClientCallPoint)
             {
             if (Arg        ===  undefined) { ThrowException (FuncName + " method of " + ObjName + " object - " + ArgName + " is undefined",   ClientCallPoint); }
             if (Arg        ===  null)      { ThrowException (FuncName + " method of " + ObjName + " object - " + ArgName + " is null",        ClientCallPoint); }
             if (typeof Arg !== "number")   { ThrowException (FuncName + " method of " + ObjName + " object - " + ArgName + " is non numeric", ClientCallPoint); }
             }

          if (Priority      ===  undefined) { Priority = 0; }
          else                              { CheckPriorityArg (Priority, "PostEvent", "EventMarshaller", "Priority", ClientCallPoint); }

          var Response =
             {
             IsResponseObj : true,

             Args          : FuncArgs,
             Func          : ResponseFunc,

             GetPriority   : function () { return Priority; },
             SetPriority   : function (NewPriority, ClientCallPoint)
                {
                CheckPriorityArg (NewPriority, "SetPriority", "Response", "NewPriority", ClientCallPoint);

                var OldPriority = Priority;

                Priority = NewPriority;

                return OldPriority;

                }

             };

          Responses.push (Response);

          if (Responses.length === EventMax) { this.Dispatch (); }

          return Response;

          },

       // -- -----------------------------------------------------------

       UnPostEvent : function (Response, ClientCallPoint)
          {
          if (Response        ===  undefined) { ThrowException ("UnPostEvent method of EventMarshaller object - Response-object argument is undefined",                        ClientCallPoint); }
          if (Response        ===  null)      { ThrowException ("UnPostEvent method of EventMarshaller object - Response-object argument is null",                             ClientCallPoint); }
          if (typeof Response !== "object")   { ThrowException ("UnPostEvent method of EventMarshaller object - Response-object argument does not refer to an object",         ClientCallPoint); }
          if (!Response.IsResponseObj)        { ThrowException ("UnPostEvent method of EventMarshaller object - Response-object argument does not refer to a Response object", ClientCallPoint); }

          for (var Idx = 0; Idx < Responses.length; Idx++)
             {
             if (Responses[Idx] == Response)
                {
                Responses.splice (Idx, 1);

                if (Responses[Idx] === null)
                   {
                   if (Idx === 0) { return null; }
                   return Responses[Idx - 1];
                   }

                return Responses[Idx];

                }

             }

          ThrowException ("UnPostEvent method of EventMarshaller object - Response object is not part of this queue", ClientCallPoint);

          },

       // -- -----------------------------------------------------------

       Dispatch : function (ClientCallPoint)
          {
          var Tmp_1 = this.PostEvent;
          var Tmp_2 = this.Dispatch;

          this.PostEvent = function (A, B, C, ClientCallPoint) { ThrowException ("PostEvent, new events cannot be posted to an EventMarshaller while it is dispatching its queue",      ClientCallPoint); };
          this.Dispatch  = function (         ClientCallPoint) { ThrowException ("Dispatch, this method cannot be called for an EventMarshaller that is already dispatching its queue", ClientCallPoint); };

          Responses.reverse ();
          Responses.sort    (function (A, B) { return A.GetPriority () - B.GetPriority (); } );

          var Response = null;

          while (Responses.length > 0)
             {
             Response = Responses.pop ();
             Response.Func (Response.Args, Response.GetPriority ());
             }

          this.Dispatch  = Tmp_2;
          this.PostEvent = Tmp_1;

          }

       };

    }
            
Copyright © Dodeca Technologies Ltd. 2008