1

Is such pattern possible in jQuery or javascript?:

$.when(function(){

    //I init many plugins here, some of them use ajax etc but I dont really control it
    //I only do something like $(div).somePlugin() here
    $("div").myPlugin()

}).done(function(){

   //and this part I want to be executed when all ajaxes and deferred stuff from when part is done
   //however I cannot go to every plugin and add something like deferred.resolve() etc.

});

and myPlugin would have for example

$.fn.myPlugin = function(){
    $(this).load(someUrl);
};

(but I cannot change myPlugin as its some external code.)

Basically I've got a lot of stuff happening and a lot of this uses async. functions. I want to execute some function when all this async. stuff is done, but I cannot change plugins code so I can't add .resolve() stuff to it.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
Adam Pietrasiak
  • 12,773
  • 9
  • 78
  • 91

3 Answers3

4

Yes, this is basically what .when does!

// changes body html
var fisrtApi = $.get("http://something/foo").then(function(r){ $("body div").html(r); }); 
// inits some API for usage
var secondApi = somePromiseReturningFucntion();
// sets a plugin on top of a page    
var somePlugin = someOtherPromiseReturningFn();


$.when(firstApi,secondApi,somePlugin).done(function(r1, r2, r3){
     // all of them ready, results are the arguments
});

It is also pretty straightforward to convert a regular non promise returning API to promises.

For example, let's do $(document).ready(function(){

// returns a promise on the document being ready
function whenDocumentReady(){
    var d = $.Deferred();
    $(document).ready(function(){ d.resolve(); });
    return d.promise();
};

Which would let you do:

$.when($.get("http://yourAPI"), whenDocumentReady()).done(function(apiResult,_){
    // access API here, the document is also ready.
});

For example - with jQuery twitter, the library provides a callback for when it's done fetching data. You would promisify it:

function getTweets(username, limit){
    var d = $.Deferred();
    $.twitter(username, limit , function(res){ d.resolve(res); });
    return d.promise();
}

Which would let you do:

$.when(getTweets("someusername"),whenDocumentReady()).done(function(tweets){
       // document is ready here _and_ the twitter data is available,
       // you can access it in the `tweets` parameter
});
Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • the point is that you have access here to your async objects. When you use external plugin that do something like ajax you dont have such acces to this object, usually its not even attached to any variable. – Adam Pietrasiak Jun 16 '14 at 12:48
  • @Kluska000 as long as those plugins return jQuery promises to mark they're done or they have a callback method you can convert yourself to promises - you can still do this. Note that calling `$.when` on a synchronous value will also work just fine and will simply resolve immediately. – Benjamin Gruenbaum Jun 16 '14 at 12:50
  • as I've wrote in question, I've got plugin that doesnt return anything (as most of plugins using ajax). Are you able to USE (not modify) my 'plugin' from question several times and wait till their 'hidden' ".load" will be done? – Adam Pietrasiak Jun 16 '14 at 12:51
  • @Kluska000 if it doesn't tell you when it's done then you can not know when it is done. Most plugins do include some indication, but if it doesn't you're stuck modifying the plugin or setting a global hook on jQuery's AJAX (which they may or may not be using by the way). – Benjamin Gruenbaum Jun 16 '14 at 12:52
  • Ok, now I'm sure you understand my issue. So it's not possible you say. – Adam Pietrasiak Jun 16 '14 at 12:53
  • Not in a way that isn't very very ugly, though most plugins do provide such a hook. – Benjamin Gruenbaum Jun 16 '14 at 12:54
  • Well then, thank you for your response, but it does not solve my problem. Solution would treat any ajax like 'try' treats any 'throw' - you dont know how it's made, but you know its there and it has been thrown. – Adam Pietrasiak Jun 16 '14 at 13:06
  • @Kluska000 this is _exactly_ what this does, this is __the__ point of promises - to give asynchronous code the properties of synchronous code! See https://gist.github.com/domenic/3889970 :) – Benjamin Gruenbaum Jun 16 '14 at 13:07
  • https://github.com/tommoor/fontselect-jquery-plugin how would you use it whit this plugin for example? Like, the only thing you do is 'using' this plugin. You dont even know when and how ajax is used there? I mean - "one of 100 things you need to wait for is getting google fonts and you use this plugin for it as you're to lazy to make your own plugin with beautiful Deferred returns :) ) – Adam Pietrasiak Jun 16 '14 at 13:09
  • AJAX is not used there, I know because I've read the source. As soon as you load that plugin it is available. – Benjamin Gruenbaum Jun 16 '14 at 13:10
  • So I've choose wrong example, but point is still same, many plugin do use ajax and they not always return handle to promise – Adam Pietrasiak Jun 16 '14 at 13:12
  • @Kluska000 try choosing a better example :) – Benjamin Gruenbaum Jun 16 '14 at 13:13
  • https://github.com/lcdsantos/jQuery-Twitter - here I can use callback so yes - it would be possible. But do you think - that assuming this plugin is using $.ajax - would it be possible to modify $.ajax so it would work without adding callbacks? – Adam Pietrasiak Jun 16 '14 at 13:15
  • Ok I get it, what you think about modifing $.ajax assuming that plugins I have use it (and that is quite possible) so I could avoid such callbacks and made evey $.ajax call creating proper promise to some global 'waiter' or something. For example on begining of my init function I would set some global name of my waiter - all ajaxes would register promises to it etc? – Adam Pietrasiak Jun 16 '14 at 13:21
  • @Kluska000 `$.ajax` _already_ returns a promise :) plugins that return $.ajax can be used directly. If you want a global waiter, check out `$.ajaxSuccess` and `$.ajaxStart` for example. I'd personally just promisify callback APIs if I were you. – Benjamin Gruenbaum Jun 16 '14 at 13:22
  • Well, you've got another green check today then ;) – Adam Pietrasiak Jun 16 '14 at 13:23
1

If that is what you are looking for, then yes, it is totally possible

$.when(sync(), async(), ajax()).done(function(s,a1, a2) {
   console.log( s + ' + ' + a1  + ' + ' + a2)     // outputs sync + async + ajax      
})

function sync() {
    return 'sync'   
}

function async() {
    var d = $.Deferred();

    setTimeout(function() {
        d.resolve('async')        
    }, 100)    

    return d;        
}

function ajax() {
    return $.post('http://jsfiddle.net/echo/html/', { html: 'ajax' })
}
vittore
  • 17,449
  • 6
  • 44
  • 82
  • What if some John Knee who made your "async" plugin function has forgot to return d or to resolve anything as he only wanted content from his json url to jump into his loved div as he didnt know anyone will need to wait for it or he was poor programer those days but he spent 2 months on some great plugin (and its quite usual sometimes)? And by the way John has minified js of his plugin becouse he didnt think anyone will need to modify it. – Adam Pietrasiak Jun 16 '14 at 12:55
  • You can't change plugin code but you can wrap init calls to those plugins in a new promise and add your logic there. – vittore Jun 16 '14 at 12:59
  • But frankly, how important plugin is? send him pull request adding proper promise handling if you need it. – vittore Jun 16 '14 at 13:01
0

I guess the only way to do it is kind of ugly.

If you cannot use deferreds and resolve method, you have no other choice than listen to changes in the dom or context (plugins usually modify the DOM or create new object in the context).

Then you will have to look for $(myElt).hasClass('<class_created_and_applied_by_my_plugin>') turning from false to true, or stuff like this.

You have to create a deferred for each plugin and wrap the previous test in a setInterval to simulate a listener, and finally resolve the deferred.

This way, you can put all your deferred into a when and be sure they are all resolved before going on.

But this is really really uggly cause you have to personalize the test for each plugin. And I guess, this will certainly slow down the browser too.

M'sieur Toph'
  • 2,534
  • 1
  • 21
  • 34
  • In modern browsers, you can use a mutation observer instead which is a lot less ugly. However, I'm convinced that the absolute majority of plugins that do asynchronous operations provice you with a hook for when those operations are done. – Benjamin Gruenbaum Jun 16 '14 at 13:24
  • Sure, me too. And if not ... take another one! ;) – M'sieur Toph' Jun 16 '14 at 13:27
  • About the mutation observer. let say the plugin only creates an object (providing special methods or properties, comming from external ressources) and does not change the DOM... I am not sure the observer will trig anything though ... – M'sieur Toph' Jun 16 '14 at 13:31