我正在寻找一个优雅,高效的解决方案来解决我的问题:
我有这个包含许多组件的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% - 什么都没有。)
答案 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);
}
请参阅:
答案 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();
});
您没有使用beforedo
,wait
,addonready
事件,而是让您的插件在您的组件已知的initQueue
中注册一个函数 - 请注意您可以选择如果您的插件不需要,则不注册回调。
粗略地说:在您的插件中,您将$doc.on('beforeDo', function(){ ... })
替换为$doc.component.initQueue.push(function(){ ... })
,如果您需要等待某些事情,则会对此事做出承诺。
然后,您可以让$.when()
函数将所有内容捆绑在一起并等待应该等待的内容。
UPDATE :实际上,$.when
期望将承诺作为单独的参数
如果存储在数组中,则需要调用$.when.apply($, array)
以符合函数的签名。
$.when(array)
会认为它的参数(数组)不是Promise,并立即解决。
答案 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);
}
就这么简单。