Critical Section in JavaScript or jQuery

Greeso picture Greeso · Mar 3, 2014 · Viewed 7.9k times · Source

I have a webpage, in which a certain Ajax event is triggered asynchronously. This Ajax section could be called once or more than once. I do not have control over the number of times this event is triggered, nor the timing.

Also, there is a certain code in that Ajax section that should run as a critical section, meaning, when it is running, no other copy of that code should be running.

Here is a pseudo code:

  1. Run JavaScript or jQuery code
  2. Enter critical section that is Ajax (when a certain process is waiting for a response callback, then do not enter this section again, until this process is done)
  3. Run more JavaScript or jQuery code

My question is, how can I run step 2 the way described above? How do I 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 either JavaScript or jQuery.

EDIT

In case you are suggesting a Boolean variable to get into the critical section, this would not work, and the lines below will explain why.

the code for a critical section would be as follows (using the Boolean variable suggestions):

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

Now this is the code for the lock section as suggested using a Boolean variable. This would not work as suggested below:

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

  2. Assume two scroll events are triggered right at almost the same moment

  3. Both call the load_data_from_database function

  4. The first event checks if window.lock is false (answer is true, so if statement is correct)

  5. The second event checks if window.lock is false (answer is true, so if statement is correct)

  6. The first event enters the if statement

  7. The second event enters the if statement

  8. The first statement sets window.lock to true

  9. The second statement sets window.lock to true

  10. The first statement runs the Ajax critical section

  11. The second statement runs the Ajax critical section.

  12. Both finish the code

As you notice, both events are triggered almost at the same time, and both enter the critical section. So a lock is not possible.

Answer

Ziggy picture Ziggy · Mar 3, 2014

I think the most helpful information you provided above was your analysis of the locking.

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

  2. Assume two scroll events are triggered right at almost the same moment

  3. Both call the load_data_from_database function

  4. The first event checks if window.lock is false (answer is true, so if statement is correct)

  5. The second event checks if window.lock is false (answer is true, so if statement is correct)

Right away this tells me that you have come to a common (and quite intuitive) misunderstanding.

Javascript is asynchronous, but asynchronous code is not the same thing as concurrent code. As far as I understand, "asynchronous" means that a function's subroutines aren't necessarily explored in depth-first order as we would expect in synchronous code. Some function calls (the ones you are calling "ajax") will be put in a queue and executed later. This can lead to some confusing code, but nothing is as confusing as thinking that your async code is running concurrently. "Concurrency" (as you know) is when statements from different functions can interleave with one another.

Solutions like locks and semaphores are not the right way to think about async code. Promises are the right way. This is the stuff that makes programming on the web fun and cool.

I'm no promise 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'm using a global promise to ensure that the ajax part of your event handler is only called once. If you scroll up and down in the fiddle, you will see that while the ajax request is processing, new requests won't be made. Once the ajax request is finished, new requests can be made again. With any luck, this is the behaviour you were looking for.

However, there is a pretty important caveats to my answer: jQuery's implementation of promises is notoriously broken. This isn't just something that people say to sound smart, it is actually pretty 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: On a personal note, I was recently in the same boat as you. As little as 3 months ago, I thought that some event handlers I was using were interleaving. I was stupefied and unbelieving when people started to tell me that javascript is single-threaded. What helped me is understanding what happens when an event is fired.

In syncronous coding, we are used to the idea of a "stack" of "frames" each representing the context of a function. In javascript, and other asynchronous programming environments, the stack is augmented by a queue. When you trigger an event in your code, or use an asynchronous request like that $.ajax call, you push an event to this queue. The event will be handled the next time that the stack is clear. So 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 fire one after the other, immediately. Then the function will end and the event you enqueued will start a new function call, (on the stack) and "hello" will appear in the console. Things get more complicated if you trigger an event in your handler, or if you trigger and event in a subroutine.

This is all well and good, but where things start to get really crappy is when you want to handle an exception. The moment you enter asynchronous land, you leave behind the beautiful oath of "a function shall return or throw". If you are in an event handler, and you throw an exception, where will it be caught? This,

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 take back this ancient and powerful oath by giving you a way to chain your callbacks together and control where and when they return. So with a nice promises API (not jQuery) you chain those callbacks in a way that lets you bubble exceptions in the way you expect, and to control the order of execution. This, in my understanding, is the beauty and magic of promises.

Someone stop me if I'm totally off.