【问题标题】:Synchronous acces to a structure filled by an asynchronous request同步访问由异步请求填充的结构
【发布时间】:2013-02-12 15:47:22
【问题描述】:

我正在尝试为在 JS/jQuery 中实现的应用程序提供状态值字典。这些值必须从服务器(ajax)中获取。我想以异步方式执行此操作,以便可以在初始化期间启动该请求。对值的实际访问应该稍后同步完成,以使代码易于理解。 目前(非常)简化的代码如下所示:

初始化:

$(document).ready(function(){
    Status.fetch();
})

结构:

Status:{
    Valid:new $.Deferred(),
    Server:{},
    /* ... */
    fetch:function(){
        if (Status.Valid.isResolved()){
            // status already present due to a past request
            return Status.Valid.promise();
        }else{
            // fetch status information from server
            $.ajax({
                type:     'GET',
                url:      'status.php'),
                cache:    true,
                dataType: 'json'
            }).done(function(response){
                $.each(response,function(key,val){
                    Status.Server[key]=val;
                });
                Status.Valid.resolve(Status.Server);
            }).fail(function(response){
                Status.Valid.reject(NaN);
            });
            return Status.Valid.promise();
        }
    }, // Status.fetch
    getServerAspect:function(aspect){
        $.when(
            Status.fetch()
        ).done(function(server){
            return server[aspect];
        }).fail(function(server){
            return NaN;
        })
        /* whyever: processing always comes here, which I don't understand... */
    }, // Status.getServerAspect
} // Status

Status.Server 被 ajax 调用填充(有效)。 Status.getServerAspect() 是一个允许同步访问存储在Status.Server 结构中的方面的方法示例( 工作)。

基本思想是结构的异步填充在结构被访问之前运行。每个(同步)访问都意味着要么立即返回所引用的方面,要么阻塞直到该值出现。但是无论我在Status.getServerAspect() 中尝试什么风格,该方法都会立即返回,而没有我可以在调用范围内使用的任何方面值。

我显然在理解使用延迟对象的基本概念方面存在问题。我一直使用它们进行同步处理,就像魅力一样。但这意味着您必须在整个应用程序中使用异步样式。对于这个特殊功能,我更喜欢同步风格,以便条件表达式保持简单:

if ('somevalue'==Status.getServerAspect('someaspect'))
    ...do something...

在异步和同步处理之间建立“桥梁”的诀窍是什么?

【问题讨论】:

  • 你用什么来“阻止”直到你有数据?据我所知,除了使 ajax 同步之外,这在现代浏览器中是不可能的
  • @KevinB 我尝试使用由异步获取过程解决的延迟对象。后来我尝试通过在$.when 中评估它来使用该延迟对象来阻止访问。至少是这样的想法......
  • 对,但这不是阻塞,它是异步的。您无法从 .done() 回调中返回数据。
  • 您需要设置 async 参数并将其设置为 false 以允许 ajax 在继续执行其余代码之前完成其请求
  • 要么让你的 ajax 同步,要么让你的代码异步。你不能两者兼得。

标签: javascript jquery asynchronous


【解决方案1】:

这将起作用,因为 setTimeout(...) 调用(异步时)仅确保 Status.Valid.state();通话并不经常进行。它仍然是一个繁忙的等待,它不仅会阻塞线程(因此,不应该在 WebWorker 之外完成)而且还会从你的 CPU 中窃取空闲时间。实际上,与其使用这种方法,不如使用同步 AJAX 请求是更好的解决方案。在为呼叫者阻塞时,不会是忙等待。但是,如果可以,您仍然应该使用纯异步实现。

【讨论】:

    【解决方案2】:

    让 getServerAspect 简单地返回承诺,然后使用它。

    getServerAspect: function(){
        return Status.Valid.promise();
    }
    

    现在您可以将它用于:

    Status.getServerAspect().done(function(data){
        if (data.someaspect == 'somevalue') {
            // do something, but don't return!
        }
    })
    

    这就是异步代码的工作原理。

    您也可以将其抽象为函数,但我发现它更难阅读。

    Status.getServerAspect: function(aspect,callback){
        Status.Valid.promise().done(function(data){
            callback(data[aspect]);
        })
    }
    

    现在你可以像这样使用它了:

    Status.getServerAspect('someaspect',function(value) {
        if ( 'somevalue' == value ) {
            // do something but don't return!
        }
    });
    

    【讨论】:

    • 当然,这是我通常做的(我写的)。但这使得条件复杂且容易出错。我正在寻找一种解决方案来保持条件从异步语句中清除。我的想法是将异步的东西移动到“包装器”方法中。但是怎么做呢?
    • 当然,但它仍将采用相同的形式,但可能更难阅读。
    • 当然,但我不在乎,因为它在包装器内,所以不碍事。必须是 celan 和 easy 的条件句,而不是 wrapper。
    • 用一个例子更新了答案
    • 对,该模式是必需的。这就是异步代码的工作原理。使用回调的唯一选择是使用async: false。您的选择,但我认为 async: false 是一个非常糟糕的主意。
    【解决方案3】:

    很难调用异步进程并尝试使其同步,最简单的方法是将函数分成两部分;一个在异步调用之前和一个之后。

    我发现最好的方法是使用 Promise 模式,或者您可以尝试使用调解器并将一系列事件传递给它,以便由您的流程的每个步骤触发:

    Good pattern to use for multiple xmlhttprequests used by different processes

    据我所知,JavaScript 并没有像其他语言那样真正具有暂停或等待功能。

    【讨论】:

      【解决方案4】:

      $.ajax 请求中,您可以将async 属性设置为true 或false,或将其传递到您的函数中

           $.when(
           Status.fetch(true)
      

      示例见下文

              Status:{
                Valid:new $.Deferred(),
                 Server:{},
                 /* ... */
                fetch:function(asyncType){      /// boolean passed as param
                   if (Status.Valid.isResolved()){
                   // status already present due to a past request
                   return Status.Valid.promise();
                   }else{
                  // fetch status information from server
              $.ajax({
                  type:     'GET',
                  url:      'status.php'),
                  cache:    true,
                  dataType: 'json',
                  async: asyncType /////// will be true or false
              }).done(function(response){
                  $.each(response,function(key,val){
                      Status.Server[key]=val;
                  });
                  Status.Valid.resolve(Status.Server);
              }).fail(function(response){
                  Status.Valid.reject(NaN);
              });
      

      【讨论】:

      • 但我不希望 ajax 请求是同步的。它应该在初始化期间触发并慢慢来。
      • 将其设置为 false,这将花费一些时间,并且在完成之前不会执行任何其他操作
      • @CR41G14 async: false 在网络工作者之外是一种非常糟糕的做法,因为它会影响用户体验。
      • @Kevin B 不在代码的其余部分依赖于 ajax 请求时
      • 总有一种方法可以更好地使用异步。
      【解决方案5】:

      我想分享我最终找到的解决方案。这不是一件优雅的事情,但它以积极的方式回答了最初的问题。因此,它提供了一种根据请求组合异步和同步访问的方法。

      我认为添加这个答案是有意义的,因为所有其他贡献者都声称这根本不可能。

      所需要的只是实现一个阻塞例程,以确保仅在异步检索的数据结构可用时才返回请求的值。所以这里没有魔法,最后只是一个简单的“等待”策略:

      getServerAspect:function(aspect){
          /* the state of the asynchronous retrieval attempt */
          var resolution=Status.Valid.state();
          while ("pending"==resolution) {
              setTimeout(function(){resolution=Status.Valid.state();},500);
          }
          /* asynchronous retrieval has finished */
          if ("resolved"==resolution){
              /* return vaule should be available */
              return server[aspect];
          }else{
              /* data could not be retrieved, something is borked */
              return NaN;
          }
      }, // Status.getServerAspect
      

      [ 这个函数很简单,是从内存中写出来的,因为我的现实世界的解决方案更复杂。 ]

      确实,该函数在等待解决方案时会阻塞 gui。这并不优雅,是的,但请注意,在原始文件中明确要求阻止行为。在正常情况下,检索应该在请求服务器方面之前很久就完成了,因此“通常”没有阻塞。对于那些不是这种情况的情况,我更愿意等待半秒,因为即使在异步处理中,最终操作也会延迟到检索完成。

      这种阻塞策略使我可以使其余代码更简单、更易于阅读,因为我不必一直使用匿名函数。相反,我可以简单地在一个简单的条件语句中访问值:

      if ("expected"==Status.getServerAspect('someaspect')){
          /* do something */
      }
      

      【讨论】:

      • setTimeout 也是异步的。我怀疑这是一个有效的阻塞解决方案。不鼓励忙着等待。
      猜你喜欢
      • 2011-06-06
      • 2016-08-19
      • 2023-04-01
      • 1970-01-01
      • 2012-01-02
      • 1970-01-01
      • 1970-01-01
      • 2018-08-11
      • 1970-01-01
      相关资源
      最近更新 更多