Critical section in JavaScript or jQuery

I have a webpage in which a specific Ajax event fires asynchronously. This Ajax section could be called one or more times. I do not control the number of times this event is triggered, as well as the time.

In addition, there is certain code in this Ajax section that must be executed as a critical section, that is, when it starts, no other copy of this code should be executed.

Here is the pseudo code:

  • Run javascript or jQuery code
  • Enter the critical section, which is Ajax (when some process is waiting for a response, then do not enter this section again until this process is complete)
  • Run more JavaScript or jQuery code

My question is: how can I perform the second step as described above? How to create / guarantee a mutual exclusion section using JavaScript or jQuery.

I understand the theory (semaphores, locks, etc.), but I could not implement a solution using JavaScript or jQuery.

EDIT

If you offer a boolean variable to go to the critical section, this will not work, and the lines below will explain why.

the code for the critical section will look like this (using sentences of a boolean variable):

load_data_from_database = function () { // Load data from the database. Only load data if we almost reach the end of the page if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) { // Enter critical section if (window.lock == false) { // Lock the critical section window.lock = true; // Make Ajax call jQuery.ajax({ type: 'post', dataType: 'json', url: path/to/script.php, data: { action: 'action_load_posts' }, success: function (response) { // First do some stuff when we get a response // Then we unlock the critical section window.lock = false; } }); // End of critical section } } }; // The jQuery ready function (start code here) jQuery(document).ready(function() { var window.lock = false; // This is a global lock variable jQuery(window).on('scroll', load_data_from_database); }); 

This is now the lock section code, as suggested using a boolean variable. This will not work as suggested below:

  • The user scrolls down (and based on the jQuery(window).on('scroll', load_data_from_database); more than one scroll event is fired.

  • Suppose two scroll events fire almost at the same moment.

  • Both calls to the load_data_from_database function

  • The first event checks if window.lock false (the answer is correct, so if the instruction is correct)

  • The second event checks if window.lock false (the answer is correct, so if the instruction is correct)

  • The first event is part of the if statement

  • The second event is part of the if statement.

  • The first statement sets window.lock to true

  • The second statement sets window.lock to true

  • The first statement launches a critical Ajax section

  • The second statement launches the critical Ajax section.

  • Both complete the code.

As you noticed, both events are triggered almost at the same time, and both are included in the critical section. Thus, blocking is not possible.

+7
javascript jquery semaphore critical-section mutual-exclusion
source share
3 answers

I think the most useful information you provided above was your blocking analysis.

  • The user scrolls down (and based on the jQuery(window).on('scroll', load_data_from_database); more than one scroll event is fired.

  • Suppose two scroll events fire almost at the same moment.

  • Both calls to the load_data_from_database function

  • The first event checks if window.lock false (the answer is correct, so if the instruction is correct)

  • The second event checks if window.lock false (the answer is correct, so if the instruction is correct)

Immediately, this tells me that you have come to a general (and rather intuitive) misunderstanding.

Javascript is asynchronous, but asynchronous code does not match parallel code. As far as I understand, “asynchronous” means that function routines are not necessarily examined in depth order, as expected in synchronous code. Some function calls (those that you call "ajax") are queued and executed later. This may lead to some confusing code, but nothing gets confused how to think that your asynchronous code is running at the same time. "Concurrency" (as you know) when instructions from different functions can alternate with each other.

Solutions like locks and semaphores are the wrong way to think about asynchronous code. Promises are the right way. This is what makes programming on the Internet fun and cool.

I do not promise a guru, but here is a working fiddle that (I think) demonstrates a fix.

 load_data_from_database = function () { // Load data from the database. Only load data if we almost reach the end of the page if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) { console.log(promise.state()); if (promise.state() !== "pending") { promise = jQuery.ajax({ type: 'post', url: '/echo/json/', data: { json: { name: "BOB" }, delay: Math.random() * 10 }, success: function (response) { console.log("DONE"); } }); } } }; var promise = new $.Deferred().resolve(); // The jQuery ready function (start code here) jQuery(document).ready(function() { jQuery(window).on('scroll', load_data_from_database); }); 

I use the global promise to ensure that the ajax part of your event handler is called only once. If you scroll up and down in the fiddle, you will see that when processing an ajax request, new requests will not be executed. Once the ajax request is complete, new requests can be made again. With any luck, this is the behavior you were looking for.

However, there are some pretty important caveats for my answer: the implementation of jQuery promises is known to be broken. It’s not just what people say that it’s great, it’s actually very important. I would suggest using a different promise library and mixing it with jQuery. This is especially important if you are just starting to learn about promises.

EDIT . In a personal note, I was recently in the same boat as you. Just 3 months ago, I thought that some of the event handlers that I used alternated. I was stunned and don't believe it when people started telling me that javascript is single-threaded. Which helped me understand what happens when a dismissal occurs.

In synchronous coding, we are accustomed to the idea of ​​a "stack" of "frames", each of which represents a function context. In javascript and other asynchronous programming environments, the stack is complemented by a queue. When you raise an event in your code or use an asynchronous request like this $.ajax call, you $.ajax event in this queue. The event will be processed the next time the stack becomes clear. For example, if you have this code:

 function () { this.on("bob", function () { console.log("hello"); }) this.do_some_work(); this.trigger("bob"); this.do_more_work(); } 

The two functions do_some_work and do_more_work will be launched one after another at once. Then the function will end, and the event you set up in the queue will trigger a new function call (on the stack) and "hello" will appear in the console. Things get more complicated if you fire an event in a handler, or if you fire an event in a subroutine.

It's all good and good, but when everything starts to get really shitty, this is when you want to handle the exception. Once you enter the asynchronous earth, you will leave the beautiful oath of "function must return or quit." If you are in an event handler and you throw an exception, where will it be caught? It,

 function () { try { $.get("stuff", function (data) { // uh, now call that other API $.get("more-stuff", function (data) { // hope that worked... }; }); } catch (e) { console.log("pardon me?"); } } 

won't save you now. promises allow you to return this ancient and powerful oath, giving you a way to combine your callbacks and control where and when they return. That way, with good API promises (not jQuery), you bind these callbacks in such a way that you can create pending errors and control the execution order. These, in my opinion, are beauty and magic promises.

Someone will stop me if I completely disconnect.

+7
source share

I would recommend a queue that allows only one element to work at a time. This will require some modification (though not very) of your critical function:

 function critical(arg1, arg2, completedCallback) { $.ajax({ .... success: function(){ // normal stuff here. .... // at the end, call the completed callback completedCallback(); } }); } var queue = []; function queueCriticalCalls(arg1, arg2) { // this could be done abstractly to create a decorator pattern queue.push([arg1, arg2, queueCompleteCallback]); // if there only one in the queue, we need to start it if (queue.length === 1) { critical.apply(null, queue[0]); } // this is only called by the critical function when one completes function queueCompleteCallback() { // clean up the call that just completed queue.splice(0, 1); // if there are any calls waiting, start the next one if (queue.length !== 0) { critical.apply(null, queue[0]); } } } 

UPDATE: Alternative solution using jQuery Promise ( jQuery 1.8+ required)

 function critical(arg1, arg2) { return $.ajax({ .... }); } // initialize the queue with an already completed promise so the // first call will proceed immediately var queuedUpdates = $.when(true); function queueCritical(arg1, arg2) { // update the promise variable to the result of the new promise queuedUpdates = queuedUpdates.then(function() { // this returns the promise for the new AJAX call return critical(arg1, arg2); }); } 

Yup, Promise of clean code was implemented. :)

+2
source share

You can wrap a critical section in a function and then change the function so that after the first start you do nothing:

  // this function does nothing function noop() {}; function critical() { critical = noop; // swap the functions //do your thing } 

User-inspired @I Hate Lazy Function in javascript that can only be called once

-2
source share

All Articles