Критический раздел в JavaScript или jQuery

у меня есть веб-страница, в которой определенное событие Ajax запускается асинхронно. Этот раздел Ajax можно вызвать один или несколько раз. У меня нет контроля над количеством раз, когда это событие запускается, и временем.

кроме того, в этом разделе Ajax есть определенный код, который должен работать как критический раздел, то есть, когда он работает, никакая другая копия этого кода не должна выполняться.

вот псевдо код:

  1. выполнить Код JavaScript или jQuery
  2. введите критический раздел, который является Ajax (когда определенный процесс ждет обратного вызова ответа, затем не входите в этот раздел снова, пока этот процесс не будет завершен)
  3. запустите больше кода JavaScript или jQuery

мой вопрос в том, как я могу запустить Шаг 2 так, как описано выше? Как создать / гарантировать раздел взаимного исключения с помощью JavaScript или jQuery.

Я понимаю теорию (семафоры, замки, ...так далее.), но я не смог реализовать решение, используя JavaScript или jQuery.

редактировать

если вы предлагаете булеву переменную, чтобы попасть в критический раздел, это не сработает, и строки ниже объяснят, почему.

код для критического раздела будет следующим (с использованием предложений булевых переменных):

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

теперь это код для раздела блокировки, как предложено с использованием булевой переменной. Это не будет работать, как предлагается ниже:

  1. пользователь прокручивает вниз, (и на основе ассоциации jQuery(window).on('scroll', load_data_from_database); запускается более одного события прокрутки.

  2. предположим, что два события прокрутки запускаются почти в один и тот же момент

  3. как позвонить load_data_from_database функции

  4. первое событие проверяет, если window.lock is false (ответ true, поэтому если оператор правильно)

  5. второе событие проверяет, если window.lock is false (ответ истинен, поэтому, если утверждение верно)

  6. первое событие входит в Оператор if

  7. второе событие входит в Оператор if

  8. первый оператор устанавливает window.lock to правда

  9. второе утверждение устанавливает window.lock в правда

  10. первый оператор запускает критический раздел Ajax

  11. второй оператор запускает критический раздел Ajax.

  12. оба заканчивают код

как вы заметили, оба события запускаются почти одновременно, и оба входят в критический раздел. Поэтому блокировка невозможна.

3 ответов


Я думаю, что наиболее полезной информацией, которую вы предоставили выше, был ваш анализ блокировки.

  1. пользователь прокручивает вниз, (и на основе ассоциации jQuery(window).on('scroll', load_data_from_database); больше чем один событие прокрутки запускается.

  2. предположим, что два события прокрутки запускаются почти в один и тот же момент

  3. как позвонить load_data_from_database функции

  4. проверка первого события если window.lock is false (ответ истинен, поэтому, если утверждение верно)

  5. второе событие проверяет, если window.lock is false (ответ истинен, поэтому, если утверждение верно)

сразу это говорит мне, что вы пришли к общему (и вполне понятный) недоразумение.

JavaScript является асинхронным, но асинхронный код-это не то же самое, что параллельный код. Насколько Я понимаю, что "асинхронный" означает, что подпрограммы функции не обязательно изучаются глубоко-в первом порядке, как мы ожидали бы в синхронном коде. Некоторые вызовы функций (те, которые вы называете "ajax") будут помещены в очередь и выполнены позже. Это может привести к некоторому запутанному коду, но ничто так не запутывает, как думать, что ваш асинхронный код работает одновременно. "Параллелизм" (как вы знаете) - это когда операторы из разных функций могут чередоваться друг с другом.

решения, такие как блокировки и семафоры, не являются правильным способом думать об асинхронном коде. обещания самый правильный способ. Это то, что делает программирование в интернете веселым и крутым.

Я не обещаю гуру, но вот рабочая скрипку это (я думаю) демонстрирует исправление.

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

я использую глобальное обещание, чтобы гарантировать, что часть ajax вашего обработчика событий вызывается только один раз. Если вы прокручиваете вверх и вниз скрипка, вы увидите, что пока запрос ajax обрабатывается, новые запросы не будут сделаны. Как только запрос ajax будет завершен, новые запросы могут быть сделаны снова. Если повезет, это именно то поведение, которое вы искали.

тем не менее, есть довольно важные предостережения к моему ответу: реализация обещаний jQuery, как известно, нарушена. Это не просто то, что люди говорят, чтобы казаться умным, это на самом деле очень важно. Я бы предложил использовать другое обещание библиотека и смешивание ее с jQuery. Это особенно важно, если вы только начинаете узнавать об обещаниях.

редактировать: лично я недавно был в одной лодке с вами. Всего 3 месяца назад я думал, что некоторые обработчики событий, которые я использовал, чередуются. Я был ошеломлен и не верил, когда люди начали говорить мне, что javascript однопоточен. Что помогло мне понять, что происходит, когда событие запускается.

в синхронном кодировании мы привыкли к идее "стека" "кадров", каждый из которых представляет контекст функции. В javascript и других асинхронных средах программирования стек дополняется очередью. Когда вы запускаете событие в своем коде или используете асинхронный запрос $.ajax вызов, вы нажимаете событие в эту очередь. Событие будет обработано в следующий раз, когда стек будет очищен. Так, например, если у вас есть это код:

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

    this.do_some_work();
    this.trigger("bob");
    this.do_more_work();
}

две функции do_some_work и do_more_work будет стрелять один за другим, немедленно. Затем функция завершится, и событие, которое вы запросили, запустит новый вызов функции (в стеке), и в консоли появится" hello". Все усложняется, если вы запускаете событие в своем обработчике или если вы запускаете и событие в подпрограмме.

это все хорошо и хорошо, но где вещи начинают становиться действительно дерьмовыми, когда вы хотите справиться с исключение. В тот момент, когда вы входите в асинхронную землю, вы оставляете позади прекрасную клятву "функция должна вернуться или бросить". Если вы находитесь в обработчике событий и создаете исключение, где оно будет поймано? Это

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

не спасет тебя. Обещания позволяют вам взять назад эту древнюю и могущественную клятву, давая вам способ связать ваши обратные вызовы вместе и контролировать, где и когда они вернутся. Поэтому с хорошим API обещаний (не jQuery) вы цепляете эти обратные вызовы таким образом это позволяет вам пузырить исключения так, как вы ожидаете, и контролировать порядок выполнения. В этом, в моем понимании, красота и магия обещаний.

кто-нибудь остановите меня, если я полностью.


Я бы рекомендовал очередь, которая позволяет запускать только один элемент одновременно. Это потребует некоторой модификации (хотя и не очень) вашей критической функции:

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's 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: альтернативное решение с использованием обещание jQuery (требует jQuery 1.8+)

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

да, обещание более чистого кода было реализовано. :)


вы можете обернуть критический раздел в функцию, а затем поменять функцию, чтобы она ничего не делала после первого запуска:

  // this function does nothing
function noop() {};

function critical() {
    critical = noop; // swap the functions
    //do your thing 

}

вдохновленный пользователем @I Hate Lazy функция в javascript, которая может быть вызвана только один раз