Overview
CreateEventMarshaller is a factory function that generates and returns
EventMarshaller objects. An EventMarshaller is a
prioritised-queuing mechanism, to which 'events' are 'posted', and which accrues those posts as a collection of
'Response' objects. Each of these carries a reference to a method, the execution of
which constitutes a response to the corresponding event.
Responses are added to an EventMarshaller's
queue until a user-defined maximum is reached, whereupon the marshaller 'dispatches' the Responses
in its queue, by removing each Response object in turn and then invoking the function to which it refers.
Responses are dispatched according to the priority that the
application may have given them, such that those with higher priorities are dispatched
before those with lower ones. Where no Responses have a priority, the marshaller in question
operates purely as a FIFO queue.
Applications
EventMarshallers offer a way of ensuring that something happens only when all other
pre-requisites have been satisfied and, furthermore, that a set of event-responses unfold in a particular order. The
lack of such guarantees underlies problems such as race conditions, and this technology obviates the inefficient and often
complex conditional logic that is employed conventionally to resolve such issues.
EventMarshallers can be used in conjunction with the
LoadLib_STH function that is also available on this site, as well as with
the
XMLHTTPRequest library. Developers who are interested in such approaches
are advised to read the relevant pages for those, as well as this one. Note that
EventMarshallers can
also be used to manage unpredictable sequences of user events such as mouse clicks on different elements in a page.
Creating Marshallers
Creating an EventMarshaller object is a simple matter of calling
CreateEventMarshaller, passing an appropriate value for the maximum number of events allowed
(the EventMax argument). Example One illustrates this.
Note that a value for EventMax must be provided, and that CreateEventMarshaller
will not accept values of less than 1. If a
threshold of 1 is stipulated then events are dispatched as soon as they arrive. This causes the marshaller
object to operate simply as a long-winded (and highly inefficient) way of calling a function.
There is no design limit on the number of EventMarshallers that can be instantiated, and a given marshaller
can be reused as many times as desired.
// Example 1 - Creating an EventMarshaller
var Marshaller = CreateEventMarshaller (3);
// Use the marshaller object hereon
Posting Events
Events are posted to a marshaller by calling its PostEvent method. This function must be passed
a reference to a function, which is the 'response' that will occur when the queue is dispatched. PostEvent
returns a Response object that holds the function reference as an attribute. Client code has
read/write access to this.
Example Two illustrates calling PostEvent, where events are dispatched in the order in which they
arrive.
Note that calling PostEvent on a marshaller that is in the process of dispatching its queue will
cause the marshaller to raise an exception. See the section on the Dispatch method for more information
and an explanation of this.
// Example 2 - Posting Events
function Response_1 () { alert ("Response_1 executed"); }
function Response_2 () { alert ("Response_2 executed"); }
function Response_3 () { alert ("Response_3 executed"); }
var Marshaller = CreateEventMarshaller (3);
Marshaller.PostEvent (Response_1);
Marshaller.PostEvent (Response_2);
Marshaller.PostEvent (Response_3);
--------------------------------------
Output:
Response_1 executed
Response_2 executed
Response_3 executed
PostEvent returns a Response object that holds the function reference
as an attribute, along with an attribute that holds the value (if any) for the FuncArgs that is supplied
by the caller.
Example Three illustrates this.
// Example 3 - Response Objects
/*
Constitution of a Response object as
if it had been defined literally
var Response =
{
Args : FuncArgs,
Func : ResponseFunc_,
GetPriority : function () { ... },
SetPriority : function (NewPriority, ClientCallPoint) { ... }
};
*/
function Response_1 () { alert ("Response_1 executed"); }
function Response_2 () { alert ("Response_2 executed"); }
function Response_3 () { alert ("Response_3 executed"); }
var Marshaller = CreateEventMarshaller (4);
Marshaller.PostEvent (Response_1);
Marshaller.PostEvent (Response_2);
var ResponseObj = Marshaller.PostEvent (Response_3, "My Value");
alert ("ResponseObj.Func : " + ResponseObj.Func);
alert ("ResponseObj.Args : " + ResponseObj.Args);
--------------------------------------
Output:
ResponseObj.Func : function Response_3 () { ... }
ResponseObj.Args : My Value
Using Minimal Syntax
The minimal syntactic forms that JavaScript permits allow event-response functions to be coded very elegantly.
In Example Four, the scenario is the same as Example Two, except that the response-function bodies are passed literally to
PostEvent.
Clearly this reduces the code volume, and precludes name-dependency errors that can arise through implementing a function separately
from the point at which one takes a reference to that function. It should also improve performance, and avoids pollution of the namespace.
// Example 4 - Using Minimal Syntax
var Marshaller = CreateEventMarshaller (3);
Marshaller.PostEvent ( function () { alert ("Response_1 executed"); } );
Marshaller.PostEvent ( function () { alert ("Response_2 executed"); } );
Marshaller.PostEvent ( function () { alert ("Response_3 executed"); } );
--------------------------------------
Output:
Response_1 executed
Response_2 executed
Response_3 executed
Passing Response-Arguments
Calls to PostEvent can also pass a parameter that will be passed to the function that constitutes
the event's response. Example Five demonstrates this.
Note that the Response object returned from a call to PostEvent subsumes
a copy of this parameter, to which client code has read/write access.
// Example 5 - Passing Arguments to an Event's Response Function
function Response_1 (Arg) { alert ("Response_1 executed - Arg : " + Arg); }
function Response_2 (Arg) { alert ("Response_2 executed - Arg : " + Arg); }
function Response_3 (Arg) { alert ("Response_3 executed - Arg.A : " + Arg.A
+ ", Arg.B : " + Arg.B); }
var Marshaller = CreateEventMarshaller (3);
var MyObj =
{
A: "Fred",
B: "Barney"
};
Marshaller.PostEvent (Response_1, 42);
Marshaller.PostEvent (Response_2, "Homer");
Marshaller.PostEvent (Response_3, MyObj);
--------------------------------------
Output:
Response_1 executed - Arg : 42
Response_2 executed - Arg : Homer
Response_3 executed - Arg.A : Fred, Arg.B : Barney
Managing Priorities
The order of Response dispatch can be controlled by giving an event a priority when calling PostEvent.
Negative as well as positive values are allowed (even non-integer values, for example: 7.698), and Responses
with higher priorities are dispatched before those with lower ones. A Response's priority can also take the
formal value of Infinity, as defined by JavaScript.
By default, Responses have a priority of zero (which is the scenario in Example Two), and where
two or more Responses have the same priority they are dispatched in the chronological order in which their
corresponding events were posted.
Example Six demonstrates assigning priorities to Response objects.
// Example 6 - Setting Event-Priorities
function Response_1 (Arg, Priority) { alert ("Response_1 executed - Priority : " + Priority); }
function Response_2 (Arg, Priority) { alert ("Response_2 executed - Priority : " + Priority); }
function Response_3 (Arg, Priority) { alert ("Response_3 executed - Priority : " + Priority); }
var Marshaller = CreateEventMarshaller (3);
Marshaller.PostEvent (Response_1, null, 2); // Medium
Marshaller.PostEvent (Response_2, null, 3); // High
Marshaller.PostEvent (Response_3, null, 1); // Low
--------------------------------------
Output:
Response_2 executed - Priority : 3
Response_1 executed - Priority : 2
Response_3 executed - Priority : 1
A Response's priority can also be accessed and manipulated via its
GetPriority and SetPriority methods. It is also passed as
the second parameter when the response function is invoked. Example Seven illustrates this.
Note that a response function that makes use of the Priority
argument, must provide an initial argument in its signature, even if it ignores the value for that argument (if any) that is passed in by
the marshaller.
Note also that it is possible to call SetPriority on a Response
object during dispatch of that Response's queue, but that this will not affect the order of dispatch.
In other words, setting a given Response's priority after the queue to which it belongs has started
dispatching is redundant.
// Example 7 - Determining an Event's Priority
function Response_1 (Arg, Priority) { alert ("Response_1 executed - Priority : " + Priority); }
function Response_2 (Arg, Priority) { alert ("Response_2 executed - Priority : " + Priority); }
function Response_3 (Arg, Priority) { alert ("Response_3 executed - Priority : " + Priority); }
var Marshaller = CreateEventMarshaller (3);
var ResponseObj = Marshaller.PostEvent (Response_1, null, 2);
Marshaller.PostEvent (Response_2, null, 3);
alert ("Response_1's Priority: " + ResponseObj.GetPriority ());
Marshaller.PostEvent (Response_3, null, 1);
--------------------------------------
Output:
Response_1's Priority: 2
Response_2 executed - Priority: 3
Response_1 executed - Priority: 2
Response_3 executed - Priority: 1
Unposting Events
While a marshaller's queue is emptied automatically as soon as its maximum-event threshold is reached,
Response objects can also be removed explicitly using the
UnPostEvent method. Callers should pass the Response
object that PostEvent generated originally.
In Example Eight, two events are posted to a marshaller that has a maximum-event threshold of three. The first
Response is then removed from the queue, after which two more events are posted,
which causes the queue to be dispatched as one would expect.
Note that, unlike PostEvent, UnPostEvent can be called
on a marshaller that is in the process of dispatching its queue. However, attempting to remove a
Response object from a queue other than the one to which it belongs will cause the marshaller
in question to raise an exception.
// Example 8 - Removing an Event from a Marshaller's Queue
function Response_1 () { alert ("Response_1 executed"); }
function Response_2 () { alert ("Response_2 executed"); }
function Response_3 () { alert ("Response_3 executed"); }
function Response_4 () { alert ("Response_4 executed"); }
var Marshaller = CreateEventMarshaller (3);
var ResponseObj = Marshaller.PostEvent (Response_1);
Marshaller. PostEvent (Response_2);
Marshaller.UnPostEvent (ResponseObj);
Marshaller. PostEvent (Response_3);
Marshaller. PostEvent (Response_4);
--------------------------------------
Output:
Response_2 executed
Response_3 executed
Response_4 executed
Note also that the value that UnPostEvent returns depends upon the size of the queue from
which the corresponding Response object is removed, along with the position it holds
in that queue with respect to other (if any) responses.
The diagram shows the three scenarios that are possible, which are:
-
If a Response is removed, where another Response follows it (in
the chronological order in which they were posted) then UnPostEvent returns a reference to
the following Response.
-
Alternatively, if there is no following Response object, but there is one ahead of the
Response that is removed, then UnPostEvent returns a reference to
that object.
-
Finally, if the Response object removed is the last in the queue then the method
returns null.
Forcing Dispatch
An EventMarshaller can also be forced to dispatch the contents of its
Response queue using the Dispatch method. Example Nine
demonstrates this, where three events are posted to a marshaller that is set to dispatch its queue after four have been
posted. However, a call to Dispatch causes this to happen explicitly and without the
posting of a fourth event.
Note that calling Dispatch or PostEvent on a marshaller
that is in the process of dispatching its queue will raise an exception, and will stop the marshaller dispatching the
remainder of that queue. The marshaller will, however, continue to operate normally after that, such that subsequent calls
to PostEvent, Dispatch etc will have the expected effect.
Such calls to Dispatch or PostEvent during dispatch
can arise when a response function executes as part of that process, and attempts (directly or indirectly) to call
PostEvent or Dispatch on the same marshaller.
It can also arise on browser platforms where a response function displays an alert box on
the user's screen. As the browser waits for the user's response, a server-generated event (for example, an XHR-based response)
could occur asychronously, and the response handler for this could attempt to call Dispatch
or PostEvent on the marshaller that is in the process of dispatching.
The EventMarshaller design precludes calling PostEvent during
queue-dispatch because it would then be possible to keep a marshaller in a state of perpetual dispatch - every time a given
response function executed, that function could post a fresh event to the same marshaller. This would put the interpreter into an
infinite loop, which would cause a browser to alert the user about a long-running script.
Additionally, the EventMarshaller design precludes calling
PostEvent during queue-dispatch because this would corrupt the dispatch order of prioritised
queues. Any new Response object would be pushed onto the end of a queue that had
already been sorted on priority, and this would preserve the dispatch order only if the new Response
had a lower priority than all the others remaining in the queue.
It would be possible to maintain the correct dispatch order by re-sorting the queue before each Response
were dispatched, but this would be inefficient and could impinge upon performance. Moreover, constant re-sorting could cause
Response 'starvation', in that posting high priority events continually, while the marshaller is
dispatching its queue, could cause lower priority Responses to remain at the end of the queue,
such that they were never dispatched. This is an old issue in computer science and resides at the heart of operating system design.
PostEvent aside, calling Dispatch on a marshaller that is already
dispatching is disallowed because the Dispatch method reverses the queue before sorting it on
priority. This causes a marshaller to operate as simple FIFO queue in the absence of any event prioritisation. Were recursive calls
to Dispatch allowed, it would corrupt the dispatch order, because an initial recursive call to
Dispatch would reverse the prioritisation, and would convert the FIFO behaviour into LIFO behaviour.
A subsequent recursive call would invert matters again, and so on, thus disrupting the dispatch order ever more.
// Example 9 - Forcing a Marshaller to Dispatch its Queue
function Response_1 () { alert ("Response_1 executed"); }
function Response_2 () { alert ("Response_2 executed"); }
function Response_3 () { alert ("Response_3 executed"); }
var Marshaller = CreateEventMarshaller (4);
Marshaller.PostEvent (Response_1);
Marshaller.PostEvent (Response_2);
Marshaller.PostEvent (Response_3);
Marshaller.Dispatch ();
--------------------------------------
Output:
Response_1 executed
Response_2 executed
Response_3 executed
Getting Event Totals
It is possible to determine the number of events that have been posted to a given marshaller by calling its
GetEventTotal method. Example Ten demonstrates this.
// Example 10 - Retrieving a Marshaller's Event Total
var Marshaller = CreateEventMarshaller (3);
Marshaller.PostEvent (...);
Marshaller.PostEvent (...);
alert (Marshaller.GetEventTotal ());
--------------------------------------
Output:
2
Getting/Setting Event Maxima
In a similar vein, a call to the GetEventMax method returns the number of events a marshaller
will accept before dispatching them. The event maximum can also be set to a new value by calling the
SetEventMax method, and Example Eleven demonstrates these two points.
Note that setting a marshallers's event maximum to a value that is less than the number of events that it holds at the time
will cause it to dispatch its queue automatically. This offers an alternative to dispatching a marshaller's queue and then setting the
maximum to a new value.
// Example 11 - Getting and Setting a Marshaller's Event Maximum
function Response_1 () { alert ("Response_1 executed"); }
function Response_2 () { alert ("Response_2 executed"); }
var Marshaller = CreateEventMarshaller (5);
alert (Marshaller.GetEventMax ());
Marshaller.PostEvent (Response_1);
Marshaller.PostEvent (Response_2);
Marshaller.SetEventMax (2);
--------------------------------------
Output:
5
Response_1 executed
Response_2 executed
Event Marshalling and XHR
One of the clear benefits of event marshalling can be found in the use of XMLHTTPRequest (XHR). XHR transactions
can be synchronous or asynchronous and, in the asychronous form, the problem of race conditions may arise.
This can happen when an application makes two or more requests to the server concurrently, where the response to one must not proceed until
the other's response has been received. If the order in which the responses are received cannot be guaranteed then the response handlers
for the XHR objects concerned must implement some form of scheduling logic to manage the execution order. Alternatively, this situation may
force a revision of the application's design on the server-side.
However, with event marshalling, the response handlers for multiple concurrent XHR-transactions can be simple functions
that, upon execution, post an event to a marshaller, stipulating an appropriate processing-function as a response. If they assign
appropriate priorities to their respective responses, then the order in which the marshaller dispatches the queue subsequently
will ensure that no response function executes at the wrong time. This obviates complex proprietary logic on the client side,
and relaxes the constraints on server-side design.
Example Twelve illustrates conducting two concurrent XHR-transactions, where processing of the response to the second transaction
is dependent on the response to the first transaction having been processed first. (Obviously, this scenario could be extended to three or more
concurrent transactions.)
Note that the example uses the small XHR-library that is also available on this site, which is why the response
handlers are (blissfully) devoid of the logic that tests the readyState
and status flags of the XHR object. The library handles that side of things, meaning that the
response handlers execute only when a transaction has completed successfully, and need do nothing other than process the server's
response.
// Example 12 - Using Event Marshalling with an XHRWrapper
function XHRResponse_1 (Data)
{
// Do something appropriate here with Data
}
function XHRResponse_2 (Data)
{
// Do something appropriate here with Data, that is
// dependent on XHRResponse_1 having executed, and
// safe in the knowledge that it really has
}
function ResponseHandler_1 (XHRObj)
{
Marshaller.PostEvent (XHRResponse_1, XHRObj.responseText, 0);
}
function ResponseHandler_2 (XHRObj)
{
Marshaller.PostEvent (XHRResponse_2, XHRObj.responseText, 1);
}
var Marshaller = CreateEventMarshaller (4);
var XHRWrapper1 = XHRFactory.CreateXHRWrapper ();
var XHRWrapper2 = XHRFactory.CreateXHRWrapper ();
XHRWrapper1.DoTxn (..., ..., true, ResponseHandler_1);
XHRWrapper2.DoTxn (..., ..., true, ResponseHandler_1);
Obviously, you may not be using the XHR library that is provided on this site, and may be working with XHR on a proprietary basis.
Given this, a more conventional response-handler, when used in conjunction with an EventMarshaller,
would look like the code in Example Thirteen.
// Example 13 - Using Event Marshalling with a Raw XHR-Object
var Marshaller = CreateEventMarshaller (4);
function ProcessData ()
{
// Do something here with
// RequestObj.responseText
// or RequestObj.responseXML
}
function Handler ()
{
if (RequestObj.readyState == 4)
{
if (RequestObj.status == 200)
{
Marshaller.PostEvent (ProcessData, XHRObj.responseText, 1);
}
else { throw new Error ("Problem in Response Handler"); }
}
}
var XHRObj = ...
XHRObj.open (...);
XHRObj.onreadystatechange = Handler;
XHRObj.send ();
Event Marshalling, XHR and Minimal Syntax
Given the minimal syntactic forms that JavaScript permits (and as illustrated in Example Four above), it is possible to
code an inline implementation of the XHR response-handler shown in Example Twelve, within the call (in that case) to
XHR.DoTxn.
Given this, it is possible to code, within that, an inline implementation of the response function that is passed to the
EventMarshaller. At the risk of obfuscation, Example Fourteen illustrates this.
// Example 14 - Using Event Marshalling with an XHRWrapper, with Minimal Syntax
var Marshaller = CreateEventMarshaller (4);
var XHRWrapper1 = XHRFactory.CreateXHRWrapper ();
var XHRWrapper2 = XHRFactory.CreateXHRWrapper ();
XHRWrapper1.DoTxn (..., // HTTP Method
..., // URL
true, // ASync flag
function (XHRObj) // Inline XHR response-handler
{
Marshaller.PostEvent (function (Data) // Inline marshaller response-func
{
// Do something appropriate here with Data
},
XHRObj.responseText, 0);
}
);
XHRWrapper2.DoTxn (..., // HTTP Method
..., // URL
true, // ASync flag
function (XHRObj) // Inline XHR response-handler
{
Marshaller.PostEvent (function (Data) // Inline marshaller response-func
{
// Do something appropriate here with Data,
// that is dependent on the first wrapper's
// response handler having executed, and safe
// in the knowledge that it really has
},
XHRObj.responseText, 1);
}
);
Note that the DoTXN method of the XHRWrapper type returns a
reference to the wrapper of which it is a member. This means that one can go still further down the minimalist line, and place
an entire call to DoTxn, as shown in Example Fourteen (and replete with its doubly-inline
function definitions), on the end of the call to CreateXHRWrapper.
If the XHRWrapper will never be reused, this means that it is not even necessary to retain the
reference to the wrapper object that is generated by the call to CreateXHRWrapper, and Example
Fifteen provides a sketch of this.
// Example 15 - Using Event Marshalling with an XHRWrapper, with Maximal Minimalism
var Marshaller = CreateEventMarshaller (4);
XHRFactory.CreateXHRWrapper ().DoTxn (..., // HTTP Method
..., // URL
true, // ASync flag
function (XHRObj) // Inline XHR response-handler
{
Marshaller.PostEvent (function (Data)
{
// Do something appropriate here with Data
},
XHRObj.responseText, 0);
}
);
XHRFactory.CreateXHRWrapper ().DoTxn (..., // HTTP Method
..., // URL
true, // ASync flag
function (XHRObj) // Inline XHR response-handler
{
Marshaller.PostEvent (function (Data)
{
// Do something appropriate here with Data,
// that is dependent on the first wrapper's
// response handler having executed, and safe
// in the knowledge that it really has
},
XHRObj.responseText, 1);
}
);
Copyright © Dodeca Technologies Ltd. 2008