jquery中的竞争条件何时/完成序列?

时间:2012-07-28 15:07:00

标签: javascript jquery deferred

我有一个基于apache,php,js / jquery的web applciation。加载文档时,将初始化应用程序的一个模块。该序列调用执行许多任务的init()函数,其中包括通过ajax获取两个html对话框并将它们插入到现有页面中。对这些对话框的引用保存为变量,因此我不必在整个脚本中指定jquery选择器,而只需使用这些变量。这种方法很好,除了在尝试将处理程序绑定到获取的对话框内的元素时变量是“未定义”的情况很少(现在然后......)。这很奇怪,因为除了那个绑定之外,对话框在整个应用程序中都是可用的。这表明ajax调用成功,但显然存在某种竞争条件,所以变量只在绑定尝试之后才设置

在我的理解中,这不应该发生,因为绑定是在when()的.done()部分完成的,并且只应在对话框的ajax检索在when()内完成之后执行。

显然我在这里缺少一些基本的东西。有人给我一个暗示吗?

以下引用了工作实现的代码摘录。它们可能在语法上看起来像某些部分一样无效,但这只是因为删除了与此处无关的代码部分。未经训练的代码工作正常。

变量:

Shorty.Tracking.Dialog.List:{};
Shorty.Tracking.Dialog.Click:{};

初始化序列:

$(window).load(function(){
  //...
  var dfd = new $.Deferred();
  $.when(
    // load layout of dialog to show the list of tracked clicks
    Shorty.Tracking.init()
  ).done(function(){
    // bind actions to basic buttons
    Shorty.Tracking.Dialog.List.find('#close').on('click',function(){
      // ...
    });
    // ...
  });
  // ...
})

缩短的初始化函数:

Shorty.Tracking.init:function(){
    // ...
    var dfd=new $.Deferred();
    // two dialogs are used by this plugin
    var dialogs={
      'list':  Shorty.Tracking.Dialog.List,
      'click': Shorty.Tracking.Dialog.Click
    };
    // load dialogs from server
    $.each(['list','click'],function(i,dialog){
      if (...){
        // ...
        dfd.resolve();
      }else{
        // load dialog layout via ajax and create a freshly populated dialog
        $.ajax({
          type:     'GET',
          url:      OC.filePath('shorty-tracking','ajax','layout.php'),
          data:     { dialog: dialog},
          cache:    false,
          dataType: 'json'
        }).pipe(
          function(response){return Shorty.Ajax.eval(response)},
          function(response){return Shorty.Ajax.fail(response)}
        ).done(function(response){
          // create a fresh dialog
          // insert it alongside the existing dialogs in the top controls bar
          $('#controls').append(response.layout);
          switch (dialog){
            case 'list':
              Shorty.Tracking.Dialog.List=$('#controls #shorty-tracking-list-dialog').first();
              break;
            case 'click':
              Shorty.Tracking.Dialog.Click=$('#controls #shorty-tracking-click-dialog').first();
          } // switch
          dfd.resolve();
        }).fail(dfd.reject)
      } // else
    }); // each
    return dfd.promise();
  },

在Bergi的回答中,我设法显然删除了原来的问题。到目前为止,我无法再检测到任何失败的绑定尝试。但是我无法完全遵循这个建议:在他的回答中,他建议删除初始化方法中的switch语句,以支持直接赋值。这肯定更优雅,但这不会奏效。我的印象是,我有一些关于javascript如何处理存储在变量中的引用和/或函数的误解。

也许你,Bergi或其他人可以解释一下:

这是上面修改的初始化方法:

  init:function(){
    if (Shorty.Debug) Shorty.Debug.log("initializing tracking list");
    // check if dialogs already exist
    if (   $.isEmptyObject(Shorty.Tracking.Dialog.List)
        && $.isEmptyObject(Shorty.Tracking.Dialog.Click) ){
      // two dialogs are used by this plugin
      var dialogs={
        'list':  Shorty.Tracking.Dialog.List,
        'click': Shorty.Tracking.Dialog.Click
      };
      // load dialogs from server
      var dfds=$.map(dialogs,function(obj,dialog){
        // load dialog layout via ajax and append it to the collection of dialogs in the controls
        return $.ajax({
          type:     'GET',
          url:      OC.filePath('shorty-tracking','ajax','layout.php'),
          data:     { dialog: dialog},
          cache:    false,
          dataType: 'json'
        }).pipe(
          function(response){return Shorty.Ajax.eval(response)},
          function(response){return Shorty.Ajax.fail(response)}
        ).done(function(response){
          // create a fresh dialog and insert it alongside the existing dialogs in the top controls bar
          $('#controls').append(response.layout);
//           obj=$('#controls #shorty-tracking-'+dialog+'-dialog').first();
          switch(dialog){
            case 'list':
              Shorty.Tracking.Dialog.List=$('#controls #shorty-tracking-list-dialog').first();
              break;
            case 'click':
              Shorty.Tracking.Dialog.Click=$('#controls #shorty-tracking-click-dialog').first();
              break;
          } // switch
        })
      }) // map
      return $.when.apply(null, dfds);
    }else{
      // dialogs already loaded, just clean them for usage
      Shorty.Tracking.Dialog.List.find('#list-of-clicks tbody tr').remove();
      new Deferred().resolve();
    } // else
  },

在中间,您会看到已注释掉的赋值语句,并且当前已被下面的switch语句替换。我尝试了几种变体,比如obj.element等,但都失败了。在函数外部,变量Shorty.Tracking.Dialog.List和Shorty.Tracking.-Dialog.Click保持为空。

我是网络内容ans js / jquery的绝对新手。但我当然很想知道这种处理参考文献的方法。

1 个答案:

答案 0 :(得分:2)

这是有问题的代码:

var dfd=new $.Deferred();
$.each(['list','click'], function(){
    ...
    dfd.resolve/fail();
});

您只创建一个Deferred,但多次解决它。因此,当第一个解决方案发生时,第二个Ajax就不会完成。

所以,改为使用两个Deferred,并通过jQuery.when()组合它们。另外,我认为你不需要自己创建Deferreds。只需从pipe()来电中获取这两个。

function init() {
   var dialogs={
      'list':  Shorty.Tracking.Dialog.List,
      'click': Shorty.Tracking.Dialog.Click
   };
   var deferreds = $.map(dialogs, function(obj, dialog) {
       return $.ajax(...).pipe(...).done(...
           // really, don't use switch here:
           obj.element = response.layout;
       }); // done() returns the Deferred
   });
   return $.when.apply(null, deferreds);
}

...

$.ready(function($) {
    init().done(...);
});

好的,我需要解释一下switch-Statement。确实,dialogs对象中的值将从[未初始化]变量中分配并保持该值(undefined),当重新分配字段时,Shorty值不会更改。 Javascript中没有“指针”。我认为只有在成员运算符中使用当前dialog变量而不是switch语句才有用。要分配到Shorty.Tracking.Dialog.List.Click,您需要使用类似

的内容
var dialogs = ["list", "click"];
var shortcut = Shorty.Tracking.Dialog; // some object
for (var i=0; i<dialogs.length; i++) {
    // loop over them,
    // do the ajax stuff
    var element = $(repsonse.layout); // get the added element from the response
    $('#controls').append(element);

    shortcut[dialogs[i]] = element;
}

(最终使用来自How do I make the first letter of a string uppercase in JavaScript?的代码段)