【问题标题】:Non-blocking Synchronous AJAX非阻塞同步 AJAX
【发布时间】:2013-07-20 15:12:57
【问题描述】:

有没有办法执行不会冻结浏览器的同步 AJAX 查询?在我看来,同步请求在大多数情况下更容易处理,但它们阻止代码的其他部分执行的事实是一个真正的杀手。有没有办法在没有负面影响的情况下获得同步 AJAX? (是的,我意识到“同步 AJAX”这个词是矛盾的。)

【问题讨论】:

  • 我不这么认为...我建议你学习一下 Promise,让使用 AJAX 更容易。
  • @hexacyanide 对不起,我想那是措辞不当。我的意思是它不会冻结浏览器或阻止我代码中的其他事件触发。
  • 即使“非阻塞同步”并不矛盾,在单执行(也称为单线程)环境中,这种构造也是不可能的。浏览器 JavaScript 中的替代方案,实际上是解决方案,是“非阻塞 异步”。

标签: javascript ajax


【解决方案1】:

我将提供一个示例,说明允许此类行为的不利影响。

假设你有这个程序:

<script>
var file = "foo.json";

function nullIt() {
    file = null;
}

function loadFile() {
    if (file != null) {
        synchronousLoad(file);//imagine the load takes 5 seconds
        alert("i just loaded: " + file);
    }
}

window.onload = loadFile;
</script>
<button onclick="nullIt()">click me</button>

这里的坏事-

  • 当synchronousLoad() 阻塞5 秒时,用户单击按钮,事件处理程序快速运行完成。
  • 现在file 变量为空。
  • synchronousLoad() 完成并返回,让下一行代码继续执行
  • 但file 现在为空,并且向用户输出的消息已损坏。

这里的真正问题是你不能再以同样的方式来推理你的代码了。只是因为一些事实是 第 5 行为真,并不意味着它在下一行仍然为真。这使得编写无错误的程序变得非常困难。

一些编程语言支持多线程,你必须处理这些问题,尽管你有工具可以帮助处理这些问题。 但是,程序员方面仍然需要付出很多额外的努力。

相对来说,使用回调做异步操作是ez-mode。

【讨论】:

  • 啊,好点子。就我个人而言,我更愿意在这种“竞争条件”(如果你可以这样称呼的话)中冒险,但很高兴看到一些理由来解释为什么事情会这样。
【解决方案2】:

没有。同步,顾名思义,就是阻塞。在该过程完成之前,任何事情都无法进行。这包括网络浏览器中 UI 的其余部分。

假定是异步的,所以最好的方法是设计代码以异步工作。

【讨论】:

    【解决方案3】:

    在即将推出的 ECMAScript 2016 (ES7) 标准中,有一组新的语言关键字旨在执行与您似乎正在寻找的内容非常相似的内容,称为 async 和 await。

    这些关键字不允许允许“非阻塞同步AJAX”,但它们确实允许您以看起来同步的方式编写异步代码。这是一个简单的例子:

    // Let's say you have an asynchronous function that you want to call in a synchronous
    // style...
    function delayedEval(delay, func) {
      // First, ensure that the function returns a Promise object. If it doesn't, wrap it with
      // another function that does.
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve(func()), delay)
      })
      // For more on Promises, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
    }
    
    // Then, declare a function as asynchronous. This causes it to return a Promise that 
    // resolves to its return value, instead of returning its return value directly.
    async function delayedHello() {
      // Inside an async function, you can call other async functions like this, which looks
      // very much like a synchronous call (even though it isn't).
      let message = await delayedEval(1500, () => "Hello, world!")
      console.log(message)
    }
    
    // This returns (a Promise) immediately, but doesn't print "Hello, world!" until 1.5
    // seconds later. (At which point it resolves the Promise.)
    delayedHello()
    

    Try in Babel

    基本上,async 和 await 不是“没有负面影响的同步 AJAX”,而是让您异步 AJAX 没有所有其负面影响。 (杂乱无章的代码,有很多处理回调的逻辑。)

    async 和 await 是 ES7 标准中 "async functions" candidate recommendation 的一部分。

    【讨论】:

      【解决方案4】:

      我认为这是不可能的。您可以使用异步方法并使用 AJAX 完成事件,而不是尝试使用同步查询。

      使用 jQuery.ajax() 的示例

      jQuery.ajax({
          url: "URL HERE",
          data: "whatever you are sending"
      }).done(function (data) {
          // Do things with data
      });
      

      因此,您可以使用异步请求并在请求完成后执行代码,而不是使用同步请求。因此,它不会冻结您的浏览器。

      【讨论】:

      • 是的,但这正是我试图通过执行同步请求来避免的。在请求完成之前,我的代码的当前执行路径中没有任何内容需要执行,因此只执行同步请求似乎更清晰。我只是不希望它在等待时阻止我的代码的其他部分。
      • @Ajedi32 它一点也不干净 - 特别是因为它不适用于单个执行上下文,例如浏览器 JavaScript 使用的上下文。此外,当使用多个执行上下文(即线程甚至延续)时,还需要处理其他注意事项和问题:删除隐式原子执行,并且必须防止 [更多形式的] 竞争条件。
      • @Ajedi32 使用 Promise(特别是在 jQuery 1.8+ 中实现并由 Promises/A 编码的 then)可以极大地清理异步代码的使用。我发现 Promise 模型比之前的成功/错误回调更加一致和清晰。
      【解决方案5】:

      由于此处其他答案中概述的原因,无法在不阻塞浏览器中的其他事件的情况下执行同步 AJAX 调用。

      但是,如果代码复杂性是您想要避免异步方法调用的主要原因,您可能会对 Javascript 交叉编译器 streamline.js 感兴趣,它允许您编写异步方法调用,就好像它们是同步的一样,并获得与异步编写调用的结果相同。

      来自项目的 GitHub 页面:

      streamline.js 是一种简化异步 Javascript 的语言工具 编程。

      而不是像这样写毛茸茸的代码:

      function archiveOrders(date, cb) {
        db.connect(function(err, conn) {
          if (err) return cb(err);
          conn.query("select * from orders where date < ?", [date], function(err, orders) {
            if (err) return cb(err);
            helper.each(orders, function(order, next) {
              conn.execute("insert into archivedOrders ...", [order.id, ...], function(err) {
                if (err) return cb(err);
                conn.execute("delete from orders where id=?", [order.id], function(err) {
                  if (err) return cb(err);
                  next();
                });
              });
            }, function() {
              console.log("orders have been archived");
              cb();
            });
          });
        });
      }
      

      你写:

      function archiveOrders(date, _) {
        var conn = db.connect(_);
        conn.query("select * from orders where date < ?", [date], _).forEach_(_, function(_, order) {
          conn.execute("insert into archivedOrders ...", [order.id, ...], _);
          conn.execute("delete from orders where id=?", [order.id], _);
        });
        console.log("orders have been archived");
      }
      

      流线型转换代码并处理回调!

      无需学习控制流 API!你只需要遵循一个简单的规则:

      用下划线替换所有回调并编写代码,就像所有 函数是同步的。

      有关 streamline.js 的更多信息,请阅读博文Asynchronous Javascript – the tale of Harry。

      【讨论】:

        猜你喜欢
        • 2018-04-06
        • 2023-03-14
        • 1970-01-01
        • 2011-10-21
        • 2013-05-07
        • 1970-01-01
        • 2016-12-14
        • 1970-01-01
        相关资源
        最近更新 更多