7

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

我有这个包含许多组件的 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");
}

我对这个解决方案不满意,因为即使我之前不需要在插件中做任何事情,我仍然必须添加处理程序以触发插件就绪(在至少一个插件中 - > 所以要么我失去了添加的灵活性/从组件​​中删除插件而不用担心readyfordo是否被触发,要么我必须在每个插件中包含处理程序——大部分是-75%——一无所获)。

4

4 回答 4

8

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

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

此外,jQuerytrigger允许您传递额外的参数。传入一个作为回调函数的函数。

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

$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 延迟
jQuery.when()
jQuery.trigger

于 2013-09-14T08:27:20.377 回答
3

在这里查看我的小提琴。您计算已完成请求的想法很好,这只是构建代码的问题。知道您需要解耦的“模块/插件”,这就是我要走的路:

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作为参数。这样你就不会用这样的元数据污染应用程序代码。

于 2013-09-14T08:41:23.167 回答
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功能之后、第二个插件更改为 trigger 之前意外触发您的事件wait

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

只要您的所有代码都在内部,您就可以控制它,但在我看来它确实很脆弱。

jQuery 的延迟/承诺:

一个通用的模式是使用 jQuery 的 Promise。jQuery 的所有异步调用现在都包含在 Promise 中,并且该库提供了一个 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();
});

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

粗略地说:在你的插件中,你替换$doc.on('beforeDo', function(){ ... })$doc.component.initQueue.push(function(){ ... }),如果你需要等待某些东西,你会围绕这个东西返回一个承诺。

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

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

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

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

小提琴

于 2013-09-16T14:59:14.607 回答
1

您使用的解决方案无法正常工作。如果您有 2 个插件,第一个接收事件并注册“等待”,那么同一个插件调用 readyfordo。2nd会发生什么?它没有机会启动。

您的每个插件都必须将自己添加到全局数组中,并且当您抛出事件时,您应该处理数组中没有任何内容的情况,否则您在解决方案中使用相同的代码而不使用 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);
}

就如此容易。

于 2013-09-14T11:02:14.613 回答