等待处理事件以继续执行触发事件的功能。

时间:2013-05-16 07:59:27

标签: javascript jquery events

我正在寻找一个优雅,高效的解决方案来解决我的问题:

我有这个包含许多组件的webapp;

一个主要组成部分包括许多随时间增长/发展的新增内容。

这个主要组件有一个功能,在实际执行它应该做的事情之前,它会触发一个事件beforedo,以便插件可以听。

dostg : function () {
   $doc.trigger('beforedo');

   //do stuff but after event is handled by the addons ?

}

在插件中

$doc.on('beforedo',function(e) {
   //do before addon stuff
}

现在那些做事之前可能涉及ajax请求或任何需要一些处理时间的事情。

我可以在ajax请求上增加一个计数器并等待它降到零,但是我想要的是一个等待所有处理程序完成工作的解决方案(因此无论做什么都可以工作)在那些插件处理程序中。)

是否有一些奇迹解决方案可以做到这一点,或者我必须在这种情况下忘记事件并采用另一种方式(迭代函数数组,插件将新函数推入数组)?

感谢您的专业知识!

-------编辑后的编辑

向@xbonez& @Sergiu Paraschiv,我应该用我正在使用的解决方案编辑问题提供赏金之前(​​解决方案我并不完全满意,因此是赏金)。

//app
var $doc = $(document.body); 


//component    
$doc.on({
    wait: function (e) { ++$doc.component.handleinprogress; },

    addonready: function () {
        --$doc.component.handleinprogress;
        if ($doc.component.handleinprogress==0) $doc.trigger('readyfordo');
    },

    readyfordo: function () {
        //do stuff after event has been handled by the addons 
    }        
});

$doc.component = {
    handleinprogress: 0,
    dostg: function () {

        $doc.component.handleinprogress = 0;
        $doc.trigger('beforedo');
    }
};

//in component addon files

$doc.on('beforedo', function (e) {

    $doc.trigger('wait');
    //do addon stuff
    $doc.trigger("addonready");
}

我对这个解决方案不满意,因为即使我不需要在插件中执行操作,我仍然需要添加处理程序以触发addonready(至少在其中一个插件中 - >所以要么我失去了从组件中添加/删除插件的灵活性,而不必担心是否会触发readyfordo,要么我必须在每个插件中包含处理程序 - 大多数是-75% - 什么都没有。)

4 个答案:

答案 0 :(得分:7)

要在执行某些代码之前等待所有处理程序完成,您应该使用jQuery的deferred API。你可以这样做:

$.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1, a2){
  // only executes after both ajax calls have completed
});

此外,jQuery的trigger允许您传递额外的参数。传递一个将成为回调函数的函数。

您的最终代码应如下所示:

$doc.trigger('beforedo', function() {
  // anything here only executes after the event has been handled
});


$doc.on('beforedo',function(e, callback) {
   //do before addon stuff
  $.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1, a2){
      // at this point, all your AJAX calls have completed
      // call the callback function
      callback();
  });
}

如果需要,当你致电callback()时,你甚至可以传递任何你可能需要作为参数传递的结果。因此,请更改.trigger()来电中的功能签名。

扩展我留下的评论,如果您愿意,可以使用常用的加载器功能:

$.when(load("page1"), load("page2")).done(function(){
 // ...
});

function load(file) {
  // return the $.ajax promise
  return $.ajax(file);
}

请参阅:

jQuery deferred
jQuery.when()
jQuery.trigger

答案 1 :(得分:3)

查看我的小提琴here。计算完成的请求的想法很好,它只是构造代码的问题。 知道你需要解耦" modules / addons"这就是我的方式:

var $doc = $(document);

function Main() {
    var handlersCount = 0;
    var completedHandlers = 0;

    function afterDostg() {
        console.log('after addon stuff');
    }

    this.dostg = function() {
        $doc.trigger('beforeDo');
    };

    this.addHandler = function() {
        handlersCount++;
    };

    this.handleCompleted = function() {
        completedHandlers++;

        if(completedHandlers === handlersCount) {
            completedHandlers = 0;
            afterDostg();
        }
    }
}

function Addon1(main) {
    main.addHandler();

    $doc.on('beforeDo', function(e) {
        console.log('addon1 stuff');
        main.handleCompleted();
    });
}

function Addon2(main) {
    main.addHandler();

    $doc.on('beforeDo', function(e) {
        setTimeout(
            function() {
                console.log('addon2 stuff');
                main.handleCompleted();
            },
            1000
        );
    });
}

var main = new Main();
var addon1 = new Addon1(main);
var addon2 = new Addon2(main);

main.dostg();

使用这种方法,插件可以包含任何内容,他们只需要通知" Main"当他们完成他们需要做的任何事情时。

如果我是你,我会更进一步,提取整个"处理程序"代码在" Main"在一个单独的类中实例化为" Main"以afterDostg作为参数。这样您就不会使用这样的元资料来识别应用程序代码。

答案 2 :(得分:3)

Softlion指出了几点,我同意。

潜在问题:

如果您的某个插件同步调用$doc.trigger("addonready"); ,您当前的实施可能会出现一个问题:

// addon 1 :
$doc.on('beforedo',function(e){

   $doc.trigger('wait');
   //do synchronous stuff :
   $('body').append('<div class="progressbar"></div>');
   console.log('addon 1 initialized');
   $doc.trigger("addonready");
}

// addon 2 :
$doc.on('beforedo',function(e){

   $doc.trigger('wait');
   $.ajax({ ...
     complete: function(){
       console.log('addon 2 initialized');
       $doc.trigger("addonready");
     }
   });
}

在这种情况下,根据回调的分辨率顺序,您可能会在第一个插件触发其readyfordo功能后,在第二个插件发生更改之前意外触发addonready事件触发wait

您的代码还依赖于所有插件始终为每个.trigger('addonready')执行一个.trigger('wait')的假设。我不知道你的代码是什么样的,或者你有多少个插件,但确保每个可能的执行路径都是这种情况是一个很大的障碍(例如:你是否测试了2^n失败案例如果你有n ajax电话吗?)

只要您的所有代码都在内部,您就可以控制它,但它对我来说似乎很脆弱。

jQuery的Deferreds / Promises:

通用模式是使用jQuery的承诺。所有jQuery的异步调用现在都包含在promises中,并且库提供了一个API,允许以相当优雅的方式操作它们 - 而且它可能已经在比你自己的代码更多的角落情况下进行了测试。

这是我的2美分:

$doc.component = {
    initQueue: [], //this array will contain callbacks, each of which
                   //is expected to return a promise
    dostg : function () {
      var queue = $doc.component.initQueue;
      var promises = [];
      var i, fnInit;
      for(i=0; i<queue.length; i++){
          fnInit = queue[i];

          //safeguard, maybe useless :
          if (typeof(fnInit) !== 'function') { continue; }

          var obj = fnInit();
          // we stack together all return values in an array :
          promises.push( obj );
      }

      // wait for all that should be waited for, then trigger your "main" event :
      $.when.apply($, promises).then(function(){  $doc.trigger('readyfordo'); });
      // $.when sorts out between promises and other results, and waits
      // only if some promises are not resolved.
   }
};

//in component addon files

$doc.component.initQueue.push(function(){

    //do addon stuff
    //if waiting for asynch event, return a promise.

    //examples :
    // jQuery ajax functions already return a Deferred :
    return $.ajax({ ... });  // just don't forget the 'return' ...
    return $.get(url, {...}, function(){ ... });

    //or, if you need a custom promise, Softlion's example :
    var deferred = $.Deferred();
    doasyncthing(function() { deferred.resolve(); });
    return deferred.promise();
});

您没有使用beforedowaitaddonready事件,而是让您的插件在您的组件已知的initQueue中注册一个函数 - 请注意您可以选择如果您的插件不需要,则注册回调。

粗略地说:在您的插件中,您将$doc.on('beforeDo', function(){ ... })替换为$doc.component.initQueue.push(function(){ ... }),如果您需要等待某些事情,则会对此事做出承诺。

然后,您可以让$.when()函数将所有内容捆绑在一起并等待应该等待的内容。

UPDATE :实际上,$.when期望将承诺作为单独的参数

如果存储在数组中,则需要调用$.when.apply($, array)以符合函数的签名。

$.when(array)会认为它的参数(数组)不是Promise,并立即解决。

fiddle

答案 3 :(得分:1)

您正在使用的解决方案无法正常运行。如果你有2个插件,第一个接收事件并注册'wait',然后同一个调用readyfordo。第二次会发生什么?它没有机会进行初始化。

每个插件都必须在全局数组中添加自己,当你抛出事件时,你应该处理数组中没有任何内容的情况,否则你在解决方案中使用相同的代码而不使用handleinprogress计数器,只是改为使用数组的长度。

您可能还对promise模式感兴趣(请参阅jquery doc中的Deferred对象),这使得“事件”的异步等待变得轻而易举。

http://api.jquery.com/category/deferred-object/

http://api.jquery.com/deferred.promise/

var plugins = [];

$.someplugin = function() {
    plugins.push(this);

    this.init = function() {
        //do some things synchronously

        //start async things. The callback is called when the async thing is finished.
        var deferred = $.Deferred();
        doasyncthing(function() { deferred.resolve(); });

        //return a promise
        return deferred.promise();
    }
}

呼叫者:

function throwEventAndWaitAsync(callback) {
    var promises = [];
    for(plugin in plugins) {
        promises.push(plugin.init());
    }

    //Wait on all promises asynchronously then call callback()
    $.when(promises).then(callback);
}

就这么简单。