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.
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 1 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
var Marshaller = createEventMarshaller (3);
// Use the marshaller object hereon
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 no attributes or methods but which can be used to 'unpost' an event subsequently should the need arise.
Example 2 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 have no effect. See the section on the dispatch method for more information and an explanation of the rationale here.
// Example 2
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
JavaScript allows a function definition (rather than a reference to a function that is defined elsewhere) to act as an argument to another function.
This means that event-response functions can be coded very elegantly. In Example 3, the scenario is the same as Example 2, except that the response-function bodies are defined inline.
Clearly this reduces the code volume, and precludes the name-clashes that can arise through implementing a function separately from the point at which one takes a reference to that function.
// Example 3
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
The order of Response dispatch can be controlled by stipulating events' priorities 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 2), 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 4 demonstrates assigning priorities to Response objects.
// Example 4
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, 2); // Medium
Marshaller.postEvent (response_2, 3); // High
Marshaller.postEvent (response_3, 1); // Low
--------------------------------------
Output:
response_2 executed - Priority : 3
response_1 executed - Priority : 2
response_3 executed - Priority : 1
The priority of an event is passed to its Response function when that function is invoked. Example 5 illustrates this.
Note that a response function that avails itself of the Priority argument, but which makes no use of the Arg argument, must provide a leading place-holder for that parameter.
Note also that it is possible to call setPriority on a Response object during dispatch of that Response's queue, but that the order of dispatch will remain unaffected. In other words, it is redundant to set a given Response's priority after the queue to which it belongs has started dispatching.
// Example 5
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, 2);
Marshaller.postEvent (response_2, 3);
Marshaller.postEvent (response_3, 1);
--------------------------------------
Output:
response_2 executed - Priority: 3
response_1 executed - Priority: 2
response_3 executed - Priority: 1
postEvent accepts a parameter that will be passed on to the response function when the relevant queue is dispatched. Example 6 demonstrates this.
// Example 6
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, 3, 42);
Marshaller.postEvent (response_2, 2, "Homer");
Marshaller.postEvent (response_3, 1, MyObj);
--------------------------------------
Output:
response_1 executed - Arg : 42
response_2 executed - Arg : Homer
response_3 executed - Arg.A : Fred, Arg.B : Barney
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 the postEvent method of the marshaller in question generated originally. If the Response has already been removed from its marshaller, or if any other object, including Response objects that belong to other marshallers, are passed to unpostEvent then the method will do nothing but return the object to the caller.
In Example 7, 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.
// Example 7
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 object 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:
An EventMarshaller can also be forced to dispatch the contents of its Response queue using the dispatch method. Example 8 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 have no 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 asynchronously, and the response handler for this could attempt to call dispatch or postEvent on the marshaller that is in the process of dispatching.
// Example 8
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
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.
EventMarshallers also preclude 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 the others remaining in the queue.
One solution would be to maintain the correct dispatch order by re-sorting the queue before each Response were dispatched, but this would be in-elegant and therefore inefficient, and could impinge upon the overall performance of a given application.
Moreover, constant re-sorting could cause Response 'starvation'. Here, posting high priority events continually, while a marshaller was dispatching its queue, could cause lower-priority Responses to remain at the end of the set, such that they were never dispatched. This is an old and fundamental issue in computer science that lies at the heart of operating-system design.
postEvent aside, calling dispatch on a marshaller that is already dispatching (which constitutes a recursive call) is disallowed because the dispatch method reverses the queue before sorting it on priority - this is what causes a marshaller to operate as simple FIFO queue in the absence of any event prioritisation.
Given this, recursive calls would cause a 're-reversion' of the response set, thus converting the FIFO behaviour into LIFO behaviour (and back again), and thus corrupting the dispatch order ever further.
It is possible to determine the number of events that have been posted to a given marshaller by calling its getEventTotal method. Example 9 demonstrates this.
// Example 9
var Marshaller = createEventMarshaller (3);
Marshaller.PostEvent (function () { });
Marshaller.PostEvent (function () { });
alert (Marshaller.getEventTotal ());
--------------------------------------
Output:
2
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 10 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 explicitly and then setting the maximum to a new value.
// Example 10
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
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 asynchronous 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 responses must arrive in a particular order. If this order cannot be guaranteed then the response handlers for the XHR objects concerned must implement some form of scheduling logic to ensure the correct 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 out of order. This obviates complex proprietary logic on the client side, and relaxes the constraints on server-side design.
Example 11 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 this 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. (See the tutorial for the XHR library for more information on this.)
// Example 11
// Contents of A.dat : 13.3690265
// Contents of B.dat : 42
var Divisor = 0;
function xHRResponse_1 (ContentsOfA) // This will execute first
{
Divisor = ContentsOfA;
}
function xHRResponse_2 (ContentsOfB) // This will execute second, safe in the knowledge
{ // that xHRResponse_1 executed previously
alert ("Ratio is " + ContentsOfB / Divisor);
}
function responseHandler_1 (XHRObj)
{
Marshaller.postEvent (xHRResponse_1, 1, XHRObj.responseText);
}
function responseHandler_2 (XHRObj)
{
Marshaller.postEvent (xHRResponse_2, 0, XHRObj.responseText);
}
var Marshaller = createEventMarshaller (2);
var XHRWrapper1 = XHRFactory.createXHRWrapper ();
var XHRWrapper2 = XHRFactory.createXHRWrapper ();
XHRWrapper1.doTxn ("A.dat", "GET", true, responseHandler_1);
XHRWrapper2.doTxn ("B.dat", "GET", true, responseHandler_2);
--------------------------------------
Output:
3.141590002832293
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 the listing.
var Marshaller = createEventMarshaller (4);
function processData ()
{
// Do something here with
// XHRObj.responseText
// or XHRObj.responseXML
}
function handler ()
{
if (XHRObj.readyState == 4)
{
if (XHRObj.status == 200)
{
Marshaller.postEvent (processData, 1, XHRObj.responseText);
}
else { throw new Error ("Problem in Response Handler"); }
}
}
var XHRObj = ...
XHRObj.open (...);
XHRObj.onreadystatechange = handler;
XHRObj.send ();
Given that JavaScript allows function referrents to be defined inline in calls to other functions (as Example 3 illustrates above), it is possible to implement the XHR response-handler shown in Example 11 within the call (in that case) to XHRWrapper.doTxn.
It follows that it is also possible to code, within that inline definition, an inline implementation of the response function that is passed to the EventMarshaller.
Furthermore, if an XHRWrapper will never be reused, it unnecessary to retain the reference to the object when createXHRWrapper is invoked. This means that one can go still further down the minimalist line by placing an entire call to doTxn on the end of the call to createXHRWrapper, thus yielding compact and elegant implementation of significant functionality.
Example 12 demonstrates this line of reasoning.
// Example 12
// Contents of A.dat : 13.3690265
// Contents of B.dat : 42
var Divisor = 0;
var Marshaller = createEventMarshaller (2);
XHRFactory.createXHRWrapper ().doTxn ("A.dat", // URL
"GET", // HTTP Method
true, // ASync flag
function (XHRObj) // Inline XHR response-handler
{
Marshaller.postEvent
(
function (ContentsOfA) // Inline marshaller response-func
{ // Will execute first
Divisor = ContentsOfA;
},
1,
XHRObj.responseText
);
}
);
XHRFactory.createXHRWrapper ().doTxn ("B.dat", // URL
"GET", // HTTP Method
true, // ASync flag
function (XHRObj) // Inline XHR response-handler
{
Marshaller.postEvent
( // Inline marshaller response-func
function (ContentsOfB) // Will execute second
{
alert ("Ratio is " + ContentsOfB / Divisor);
},
0,
XHRObj.responseText
);
}
);
--------------------------------------
Output:
3.141590002832293
EventMarshaller_DbC is an optional adjunct that uses the AspectJS AJS-object to apply a wrapper to createEventMarshaller. The prefix that is attached tests the parameters provided to the function, and throws an exception whenever bad or missing arguments are detected, whereas the suffix applies affixes to the EventMarshaller objects that createEventMarshaller generates. Those affixes perform similar argument-checking, and also throw an exception whenever bad parameters arise.
To use eventMarshaller_DbC, your code should import the function's definition and the AJS object-definition, before calling eventMarshaller_DbC just once and as soon as possible in the application's run. Example 13 illustrates this.
Examining the parameters that eventMarshaller_DbC accepts:
Failure to supply the first three parameters, or to provide arguments of an incorrect type, will cause eventMarshaller_DbC to throw an Error object, whose message member will contain a detailed description of the problem (including the contents of CallPoint, if the caller provides a value for that argument).
Additionally, and given that it is necessary to call eventMarshaller_DbC only once, calling it again will also raise an exception.
Note that, once enabled, there is no way to turn error checking off during a given run of the application.
<!-- Example 13 -->
<html ... >
<head ... >
<script type = "text/javascript" src = "AJS.js" ></script>
<script type = "text/javascript" src = "EventMarshaller.js" ></script>
<script type = "text/javascript" src = "eventMarshaller_DbC.js"></script>
<script type = "text/javascript">
eventMarshaller_DbC (this, "createEventMarshaller", AJS, "Example 13 Point A");
...
</script>
</head>
<body> ... </body>
</html>
Following a call to eventMarshaller_DbC, subsequent calls to createEventMarshaller and the methods of the EventMarshaller objects it generates will continue to operate normally, but with the benefit of rigorous checks on any arguments that client code supplies. To disable this error checking, and thereby lose the overhead it entails, simply comment out the call to eventMarshaller_DbC, and disable any statement that loads eventMarshaller_DbC's code.
The second effect of eventMarshaller_DbC is that it appends a CallPoint argument to the signatures of createEventMarshaller and of the methods of EventMarshaller objects. This plays the same role as the argument accepted by eventMarshaller_DbC, and this augmentation is the same effect that aJS_DbC has on AJS object-methods.
The effects of using eventMarshaller_DbC are illustrated in Example 14, where a selection of deliberately erroneous calls are made and trapped. (Note that the event-marshaller library is furnished with an HTML file that tests eventMarshaller_DbC exhaustively.)
Unfortunately, it is not possible for eventMarshaller_DbC to detect calls to the dispatch and postEvent methods of a given marshaller when that marshaller is already in the process of dispatching its queue.
In principle, this could be effected by setting a prefix on the dispatch method, such that, when that method was invoked, the prefix would apply a further, temporary, prefix to the dispatch method (and as the first in the execution order). It would also apply a temporary prefix to postEvent, and both these temporaries would throw an exception if either of their interceptees were invoked.
The permanent prefix attached to the dispatch method would also set a suffix on that method, which, following return from dispatch, would remove the two temporary prefixes. The suffix could be applied with an execution count of 1, meaning that it would be detached from dispatch automatically as soon as that method returned, which would thereby return matters to normal.
This is an elegant scheme, and (from a general perspective) is an excellent example of the value of method-call interception and AspectJS when developing complex systems.
Unfortunately, however, it is also flawed because a Response function could throw an exception. This would preclude the suffix's execution, meaning it would not get a chance to remove the prefixes, and their presence thereafter would perpetuate this situation permanently.
This is why the dispatch and postEvent methods are implemented such that they do nothing when called on a marshaller that is dispatching its queue.
// Example 14
eventMarshaller_DbC (this, "createEventMarshaller", AJS);
function responseFunc (Arg) { }
try { createEventMarshaller (); } catch (E) { alert (E.message); }
var Marshaller = createEventMarshaller (10);
var EventObj = Marshaller.postEvent (responseFunc, 1, "ResponseFunc arg");
var SomeObj = { };
try { Marshaller.setEventMax (-1, "A"); } catch (E) { alert (E.message); }
try { EventObj.setPriority ("", "B"); } catch (E) { alert (E.message); }
try { Marshaller.postEvent (null, 1, null, "C"); } catch (E) { alert (E.message); }
try { Marshaller.unpostEvent (SomeObj, "D"); } catch (E) { alert (E.message); }
--------------------------------------
Output:
Error in call to createEventMarshaller - EventMax is
undefined. Client-code call point: undefined
Error in call to createEventMarshaller - value of
EventMax is less than 1. Client-code call point: A
Error in call to setPriority method of EventMarshaller
Response-object - NewPriority is non-numeric.
Client-code call point: B
Error in call to postEvent method of EventMarshaller
object - ResponseFunc argument is null. Client-code
call point: C
Error in call to unpostEvent method of EventMarshaller
object - Response-object argument does not refer to a
Response object. Client-code call point: D