jCon-Q-rency enables synchronization between different jQuery code sections and is meant to simulate or manage concurrency.
It can be helpful to synchronize different asynchronous code sections that don't need to be aware of each other...
Of course, JavaScript was originally single threaded, thus it couldn't offer true parallelism.
However, combined with HTML5 webworkers, AJAX calls and event listeners, jCon-Q-rency should be a lot of fun to use !
Please do not directly use these download links as script sources on your pages (no hotlinking) ! Thanks.
You may use jCon-Q-rency under the terms of either the MIT License or the GNU General Public License (GPL) Version 2.
Compatibility Opera, Safari, Firefox, Chrome, IE 6+ :: jQuery 1.3.2+.
Here you also have a plain JavaScript version. With this version, you can use the APi functions on any JavaScript Object.
The present documentation is jCon-Q-rency centric but it also applies to JS Concurrency.
Please do not directly use these download links as script sources on your pages (no hotlinking) ! Thanks.
You may use JS Concurrency under the terms of either the MIT License or the GNU General Public License (GPL) Version 2.
Compatibility Opera 11.6+, Safari 5.1.7+, Firefox 4.0+, Chrome 5.0+, IE 9.0+.
callback The function to execute when notified. If a queue reference is specified, it will be passed as an argument to that function.
queue The reference of the queue to which the element shall be added.
Returns The calling element or set itself.
queue The reference of the queue in which an element shall be notified.
Returns The notified element or set and undefined if none has been notified.
queue The reference of the queue in which an element shall be notified.
Returns The calling element or set itself, for chaining.
queue The reference of the queue in which all elements shall be notified.
Returns The calling element or set itself.
queue The reference of the queue in which the element will be searched.
Returns true or false whether the queue exists or not.
queue The reference of the queue to consider.
Returns The length of the queue or -1 if the queue doesn't exist.
queue The reference of the queue to delete. If no queue reference is specified, an empty default queue will be re-generated.
Returns The calling element or set itself.
You can use any JavaScript object or primitive value. The same object should be used for all references to a given queue.
Just declare a new queue using .wait(object) and a singleton queue referenced by that object will be created.
If you specify no queue reference, the default queue will be used.
Warnings :
.: "A String"=="A String" and "A String"==="A String" since those are the same value and not objects;
and using the same primitive value to reference queues will actually return the same singleton queue.
.: Multiple arrays like for instance ["a", "b", "c"] - containing exactly the same primitive values (even not ordered the same way or/and with different keys) - will be recognized as the same reference even if they are not indeed the same object; so they will actually reference the same queue.
This is to enable using identical jQuery result sets as identical references in different code sections.
So don't play too much with arrays, prefer other object types !
.notify(), .notifyStay(), .notifyAll(), .waitingQueueLength() and .deleteWaitingQueue() can be used on any jQuery element or set; the effect will be the same.
.wait() must be used on the jQuery element or set to add to the considered queue. When notified, this designates the whole set. Use .each() to treat each element separately.
.isWaiting() must be used on the jQuery element to search for in the considered queue.
Warning : if you call a .wait() on a query ( ex: $(".element") ) returning a set AND,
between that call and a .isWaiting() call on a second identic query, at least one element of the result set has been removed or one has been added,
THEN .isWaiting() will return false since the results of those two queries aren't identical.
Example
<div id="element1" class="element"></div>
<div id="element2" class="element"></div>
WRONG:
$(".element").wait(function() {});
$("#element1").remove();
alert( $(".element").isWaiting() ); // alerts "false"
GOOD:
var mySet = $(".element").wait(function() {});
$("#element1").remove();
alert( mySet.isWaiting() ); // alerts "true"
Warning : the following example will return true for the same reasons than for queue references.
So don't play too much with arrays, prefer other object types !
var myArray = [1, 2, 3];
$(myArray).wait(function() {});
myArray[0] = 3;
alert( $([3, 2, 3]).isWaiting() ); // alerts "true"
When .notifyAll() is called, all the elements of the considered queue are notified before any gets its callback function executed;
the queue is emptied before any element executes its callback function. Thus, callback functions are executed asynchronously.
Note: first-in/first-out notification should occur but is not guaranteed. You are encouraged to study the source code to have a better understanding of the mechanics involved.
Some of the following examples, we use the html code:
<div id="element1"></div>
<div id="element2"></div>
<div id="element3"></div>
Sometimes we refer to queue references as monitors.
The "..." means a separation, like an asynchronization (another code section).
In any case, be careful with scopes to be able to access your queue references in all code sections your need to use them !!
$("#element1").wait( function() {
this.html("[..]");
});
...
$("#element2").notify(); // reminder : same effect as $("#element1").notify()
$.myqueue = "[..]"; // used as monitor in callback function
var callback = function(monitor) {
this.html(monitor);
}
$("#element1").wait(callback, $.myqueue);
$("#element2").wait(callback, $.myqueue);
...
$("#element3").notifyAll($.myqueue);
$({value: 1234}).wait( function() { alert(this.prop("value")) } );
...
$({}).notify();
$.monitor = function() { return "[..]" };
$({}).wait( function(moni) { alert(moni.call()) }, $.monitor );
...
$({}).notify($.monitor);
$({ toDelay: function() { return "[..]" } })
.wait( function() { alert(this.prop("toDelay").call()) } );
...
$({}).notify();
var myText = "[..]"; // not used in other code sections so doesn't need to be a global variable
$("#element1").wait( function() {
// SOME CODE HERE
this.wait( function() {
this.html( myText );
});
});
...
$("#element2").notify();
...
$("#element3").notify();
Do not use "while" or "do-while" loops !! Use the following recursion proposition using "if-else" instead :
$.test_value = 0;
$.myText = "[..]"; // used as monitor in callback function
...
var callback = function(monitor) { // callback function
if( $.test_value == 0 ) {
this.wait(callback, monitor);
}
else {
this.html(monitor); // the job to do
}
}
if( $.test_value == 0 )
$("#element1").wait(callback, $.myText); // enter the recursive loop
else
callback.call($("#element1"), $.myText); // direct execution
...
setTimeout( function() { $.test_value++ }, 1000);
...
setTimeout( function() { $("#element2").notify($.myText) }, 2000);
Here, each element fetches data from a given url but displays it only when all 3 are ready.
var call1 = $.ajax({
url: "[..]"
}).done(function(data) {
$(this).wait( function() {
$("#element1").html(data); // the job to do
});
if( $(this).waitingQueueLength() == 3 ) // test if all three waiting
$(this).notifyAll();
});
var call2 = $.ajax({
url: "[..]"
}).done(function(data) {
$(this).wait( function() {
$("#element2").html(data); // the job to do
});
if( $(this).waitingQueueLength() == 3 ) // test if all three waiting
$(this).notifyAll();
});
var call3 = $.ajax({
url: "[..]"
}).done(function(data) {
$(this).wait( function() {
$("#element3").html(data); // the job to do
});
if( $(this).waitingQueueLength() == 3 ) // test if all three waiting
$(this).notifyAll();
});
The goal here is to access and modify a "shared" variable (value) concurrently.
Each WebWorker must fetch that value, increment it and send it back for update 4 times (4 rounds).
Since we declare 3 WebWorkers, the final result shall be 12.
Each time a WebWorker updates the value, it must notify all others so one can access that value in turn.
The goal is that no other WebWorker can access the value while one is doing calculations on it; until the lock is released.
The concurrency management happens in the Main Window.
The value accessed concurrently is $.concurrentAccess.value.
We use $.concurrentAccess.wait as Lock.
This example actually uses the logic of semaphore.
The Main Window being single threaded and the execution of sequential code sections being atomic, we don't need synchronized functions like in Java.
You can communicate with WebWorkers only through messages (strings or JSON objects).
Here, all messages are JSON objects containing at least a header defining the type of the message.
Messages :
To a worker :
{"header": "start"} : tells the worker to start (init)
{"header": "process", "value": X } : tells the worker to process the passed value X
From a worker :
{"header": "ready"} : signals that the worker is willing to access the value ... put it in the waiting queue
{"header": "result", "value": X } : contains the return processed value X
This example is quite basic since it shows how to access concurrently only one variable... so it executes correctly; but in your use-case, be careful with deadlocks, safety, error handling, etc.
Of course, your browser needs to support HTML5 for this code to run !
CODE
/** Main Window **/
$.concurrentAccess = { value: 0, wait: false }; // monitor
var access = function(monitor) { // callback function
if( monitor.wait ) {
this.wait(access, monitor);
}
else { // access granted
monitor.wait = true; // lock the access
// [1] access and send the value to the worker
this[0].postMessage( {"header": "process", "value": monitor.value} );
}
}
var processMessage = function(e) { // workers messages processor
var data = e.data;
switch(data.header) {
case "ready": {
if($.concurrentAccess.wait)
$(this).wait(access, $.concurrentAccess); // get in the queue
else
access.call($(this), $.concurrentAccess); // direct execution
}; break;
case "result": {
$.concurrentAccess.value = data.value; // [3] update the value
$.concurrentAccess.wait = false; // release the lock
$(this).notifyAll($.concurrentAccess); // a simple .notify(..) could do
$("#element1").html("current value: " + data.value); // log the value after each update
}; break;
default: break;
}
};
var worker1 = new Worker('WebWorker.js');
var worker2 = new Worker('WebWorker.js');
var worker3 = new Worker('WebWorker.js');
worker1.addEventListener("message", processMessage, false);
worker2.addEventListener("message", processMessage, false);
worker3.addEventListener("message", processMessage, false);
worker1.postMessage( {"header": "start"} );
worker2.postMessage( {"header": "start"} );
worker3.postMessage( {"header": "start"} );
...
/** WebWorker.js **/
var count; // left rounds counter
self.addEventListener('message', function(e) {
var data = e.data;
switch(data.header) {
case "start": {
count = 4; // init the rounds counter
self.postMessage( {"header": "ready"} );
}; break;
case "process": {
if(count > 0) {
var value = data.value; // read the passed value
count--;
setTimeout(function() { // delay to simulate long calculation
value++; // [2] modify the local value
if(count > 0) // signal that the worker has one (more) round(s) left
self.postMessage( {"header": "ready"} );
// return the processed value
self.postMessage( {"header": "result", "value": value} );
}, 500 );
}
else // terminate the worker if all rounds done
self.close();
}; break;
default: break;
}
}, false);
If you find this plugin useful, all donations are sincerely appreciated.
Available soon...