Waiting for an event to be processed to continue executing the function that raised the event.

I am looking for an elegant, effective solution to my problem:

I have this webapp with many components;

One of the main components includes many additions that will grow / develop over time.

This main component has a function in which before doing what it should do, it fires a beforedo event so that add-ons can listen.

dostg : function () { $doc.trigger('beforedo'); //do stuff but after event is handled by the addons ? } 

In addon codes

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

Now, those who, before performing any actions, may include an ajax request or something that will require some processing time.

I could increase the counter on ajax requests and wait until it drops to zero, but what I need is a solution that just waits for all the handlers to finish their work (and therefore it will work regardless what is done in those addon handlers).

Is there any miracle solution for this, or should I forget about the event in this situation and go the other way (array of functions for iterating, adding, adding a new function to the array)?

Thanks for your experience!

------- EDIT NEXT PAGE

Apologize to @xbonez and @Sergiu Paraschiv, I should edit the question using the solution that I am using now before offering the award (a solution that I am not completely satisfied, therefore, generosity).

 //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"); } 

I am not satisfied with this decision, because even if I do not need to do something in the addon, I still need to add a handler to call addonready (in at least one of the addon → so that I lose the flexibility to add / remove the addon from the component, without worrying about whether readyfordo starts, or I should include a handler in each add-on - basically -75% - for nothing).

+6
source share
4 answers

To wait for all handlers to complete before executing some code, you must use the jQuery deferred API. You can do something like this:

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

In addition, jQuery trigger allows you to pass additional parameters. Pass a function that will be a callback function.

Your last code should look something like this:

 $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(); }); } 

If necessary, when you call callback() , you can even pass any result that you might need to pass as an argument. Accordingly, change the function signature to your .trigger() call.

By expanding the comment I left below, you can have a generic bootloader function if you want:

 $.when(load("page1"), load("page2")).done(function(){ // ... }); function load(file) { // return the $.ajax promise return $.ajax(file); } 

Cm:

jQuery postponed
jQuery.when ()
jQuery.trigger

+7
source

Check out my fiddle here . Your idea of ​​counting completed queries is good, it's just a matter of structuring the code. Knowing that you need untied "modules / add-ons", I would go like this:

 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(); 

Using this method, add-ons may have something in them, they just have to notify the "Home" when they complete everything they need.

If I were you, I would go even further and extract all the code of the “handlers” in the “Main” into a separate class created as a public property in the “Main” with afterDostg as a parameter. Thus, you will not publish application code with such metafiles.

+3
source

Softlion has indicated several points with which I agree.

Potential problems:

One problem may arise with your current implementation if one of your add-ons calls $doc.trigger("addonready"); synchronously:

 // 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"); } }); } 

In this case, depending on the resolution order of your callbacks, you may accidentally fire your readyfordo event after the first addon has activated its addonready function, and before the second one fails to fire wait .

Your code is also based on the assumption that all your add-ons will always execute exactly one .trigger('addonready') for each .trigger('wait') . I don’t know what your code looks like or how many add-ons you have, but ensuring that this is the case for every possible execution path is quite complicated (for example: did you test cases of 2^n failure if you have n ajax calls?)

As long as all your code is inside the house, you can control it, but to me it seems fragile.

jQuery Deferred / Promises:

A common template is to use jQuery promises. All jQuery asynchronous calls are now wrapped in promises, and the library offers an API that allows you to manage them in a rather elegant way - plus it has probably been tested in more complex cases than your own code.

Here are my 2 cents:

 $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 example : var deferred = $.Deferred(); doasyncthing(function() { deferred.resolve(); }); return deferred.promise(); }); 

Instead of using beforedo , wait , addonready , you have plugins that register the function in the initQueue known to your component - note that you can not register a callback if your addon does not need it.

Roughly speaking: in your add-ons you replace $doc.on('beforeDo', function(){ ... }) with $doc.component.initQueue.push(function(){ ... }) , and if you need to wait for something, you return a promise around it.

Then you can let the $.when() function take care of $.when() everything together and waiting for what is expected.

UPDATE: $.when expects Promises as separate arguments

If it is stored in an array, you need to call $.when.apply($, array) to match the signature of the function.

$.when(array) will assume that its argument (array) is not a promise and will immediately resolve.

fiddle

+3
source

The solution you are using is not working correctly. If you have 2 add-ons, the 1st one receives the event and "wait" is registered, then the same call is prepared. What happens to the second? He will not be able to initialize.

Each of your add-ons should add itself to the global array, and when you throw your event, you should handle the case when there is nothing in the array, otherwise you use the same code in your solution without using the handleinprogress counter, just use the length of the array.

You may also be interested in a promise template (see the “Pending Object in jquery doc” section) that expects to wait asynchronously for an “event” in the wind.

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(); } } 

Subscriber:

 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); } 

just as easy.

+1
source

Source: https://habr.com/ru/post/945130/


All Articles