jCon-Q-rency

Overview

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 !

Download

Please do not directly use these download links as script sources on your pages (no hotlinking) ! Thanks.


jCon-Q-rency v0.1c Get the source! ( License )


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+.

None jQuery Version

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.


JS Concurrency v0.1c Get the source! ( License )


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+.

APi

.wait( callback [, queue] )

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.

.notify( [queue] )

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.

.notifyStay( [queue] )

queue The reference of the queue in which an element shall be notified.

Returns The calling element or set itself, for chaining.

.notifyAll( [queue] )

queue The reference of the queue in which all elements shall be notified.

Returns The calling element or set itself.

.isWaiting( [queue] )

queue The reference of the queue in which the element will be searched.

Returns true or false whether the queue exists or not.

.waitingQueueLength( [queue] )

queue The reference of the queue to consider.

Returns The length of the queue or -1 if the queue doesn't exist.

.deleteWaitingQueue( [queue] )

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.

What can be used as queue reference?

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 !

What can these functions be used on?

.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"

The behaviour of .notifyAll()

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.

Code suggestions

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 !!

Basic


  $("#element1").wait( function() {
     this.html("[..]");
  });
  
  ...
  
  $("#element2").notify(); // reminder : same effect as $("#element1").notify()
				

Function re-use + monitor


  $.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);
				

Wait with an JSON Object


  $({value: 1234}).wait( function() { alert(this.prop("value")) } );
		
  ...
  
  $({}).notify();				
				

A function as monitor


  $.monitor = function() { return "[..]" };
  
  $({}).wait( function(moni) { alert(moni.call()) }, $.monitor );
		
  ...
  
  $({}).notify($.monitor);				
				

Delay a function's execution


  $({ toDelay: function() { return "[..]" } })
  .wait( function() { alert(this.prop("toDelay").call()) } );
		
  ...
    
  $({}).notify();
				

Nested/Consecutive waits


  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();

				

Conditioned execution

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);
				

AJAX calls synchronization

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();
      
  });
				

True concurrency with WebWorkers

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);
				

Generosity

If you find this plugin useful, all donations are sincerely appreciated. :)

Tags

jQuery JavaScript concurrency control library synchronization parallelism mutex semaphore java.util.concurrent race conditions mutual exclusion threads deadlock wait notify asynchronous callback event listener AJAX calls processus WebWorker...

Forum

Available soon...

HTML Comment Box is loading comments...