JavaScript或jQuery中的关键部分

时间:2014-03-03 15:41:27

标签: javascript jquery semaphore critical-section mutual-exclusion

我有一个网页,其中异步触发某个Ajax事件。这个Ajax部分可以被调用一次或多次。我无法控制触发此事件的次数,也无法控制时间。

此外,Ajax部分中有一些代码应作为关键部分运行,这意味着,当它运行时,不应该运行该代码的其他副本。

这是一个伪代码:

  1. 运行JavaScript或jQuery代码
  2. 输入Ajax的关键部分(当某个进程正在等待响应回调时,请不要再次输入此部分,直到此过程完成)
  3. 运行更多JavaScript或jQuery代码
  4. 我的问题是,如何按照上述方式运行第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是否 false (答案是真的,所以如果陈述是正确的话)

    5. 第二个事件会检查window.lock false (答案是否为真,所以如果声明正确的话)

    6. 第一个事件进入if语句

    7. 第二个事件进入if语句

    8. 第一句话将window.lock设为 true

    9. 第二个语句将window.lock设置为 true

    10. 第一个语句运行Ajax关键部分

    11. 第二个语句运行Ajax关键部分。

    12. 两者都完成了代码

    13. 正如您所注意到的,两个事件几乎同时被触发,并且都进入临界区。因此无法锁定。

3 个答案:

答案 0 :(得分:8)

我认为您上面提供的最有用的信息是您对锁定的分析。

  
      
  1. 用户向下滚动,(并且基于关联jQuery(window).on('scroll', load_data_from_database);不止一个   滚动事件被触发。

  2.   
  3. 假设几乎在同一时刻触发了两个滚动事件

  4.   
  5. 两者都调用load_data_from_database函数

  6.   
  7. 第一个事件会检查window.lock是否 false (答案是真的,所以如果陈述是正确的话)

  8.   
  9. 第二个事件会检查window.lock false (答案是否为真,所以如果声明正确的话)

  10.   

这就告诉我你已经达成了一个共同的(而且非常直观的)误解。

Javascript是异步的,但异步代码与并发代码不同。据我所知,“异步”意味着函数的子程序不一定按照我们在同步代码中所期望的深度优先顺序进行探索。一些函数调用(你称之为“ajax”的函数)将被放入队列并稍后执行。这可能会导致一些令人困惑的代码,但没有什么比认为您的异步代码同时运行更令人困惑。 “并发”(如您所知)是来自不同函数的语句可以相互交错的时候。

锁和信号量等解决方案不是考虑异步代码的正确方法。 Promises是正确的方法。这就是让网络编程变得有趣和酷炫的东西。

我不是承诺大师,但这是一个working fiddle(我认为)演示了修复。

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的承诺实施是众所周知的。这不仅仅是人们所说的听起来很聪明的东西,它实际上非常重要。我建议使用不同的promise库并将其与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_workdo_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?");
    }
}

现在不会救你。 Promise允许你通过给你一种方法将你的回调链接在一起并控制他们返回的地点和时间来收回这个古老而强大的誓言。因此,使用一个很好的promises API(而不是jQuery),你可以用一种方式链接这些回调,让你以你期望的方式冒泡,并控制执行的顺序。在我的理解中,这是承诺的美丽和魔力。

如果我完全离开,有人会阻止我。

答案 1 :(得分:2)

我建议一个只允许一次运行一个项目的队列。这需要对您的关键功能进行一些修改(尽管不多):

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

更新:使用jQuery's Promise的替代解决方案(需要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);
    });
}
是的,清洁代码的承诺得以实现。 :)

答案 2 :(得分:-2)

您可以将关键部分包装在一个函数中,然后交换该函数,使其在首次运行后不执行任何操作:

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

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

}

受用户启发@I Hate Lazy Function in javascript that can be called only once