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是否为false (回答为true,所以if语句是否正确)

  5. 第二个事件检查window.lock是否为false (回答为true,所以如果语句正确)

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

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

  8. 第一个语句将window.lock设置为true

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

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

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

  12. 两者都完成了代码

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

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

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

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

  3. 两者都调用load_data_from_database函数

  4. 第一个事件检查window.lock是否为false (回答为true,所以if语句是否正确)

  5. 第二个事件检查window.lock是否为false (回答为true,所以如果语句正确)

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

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的承诺实现是众所周知的。 这不仅仅是人们所说的听起来很聪明的东西,它实际上非常重要。 我建议使用不同的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”将出现在控制台中。 如果在处理程序中触发事件,或者在子例程中触发事件,事情会变得更复杂。

这一切都很好,但是当你想要处理exception时,事情开始变得非常糟糕。 当你进入异步土地的那一刻,你留下了“一个function应该返回或抛出”的美丽誓言。 如果您在事件处理程序中,并且抛出exception,它将被捕获到哪里? 这个,

 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),你可以用一种方式链接这些回调,让你以你期望的方式冒泡,并控制执行的顺序。 在我的理解中,这是承诺的美丽和魔力。

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

我建议一个只允许一次运行一个项目的队列。 这需要对您的关键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'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的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); }); } 

是的,清洁代码的承诺得以实现。 🙂

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

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

灵感来自javascript中的用户@I Hate Lazy 函数,只能被调用一次