3

So let's say we load a bunch of scripts via $.getScript:

$.getScript( 'js/script1.js' );
$.getScript( 'js/script2.js' );
$.getScript( 'js/script3.js' );

Now, I'd like to invoke a handler when all those scripts finished loading. I tried binding a handler for the global ajaxStop event. According to the docs, the ajaxStop global event is triggered if there are no more Ajax requests being processed.

$( document ).ajaxStop( handler );

but it doesn't work (the handler is not invoked).

Live demo: http://jsfiddle.net/etGPc/2/

How can I achieve this?

7
  • 1
    Have you tried combining the promises with $.when()? (Or however it is that one does that ...) Commented Nov 6, 2011 at 17:25
  • @Pointy No, I have yet to learn how to use deferreds in jQuery. For now, I would like to understand why .ajaxStop() fails - I must be using it wrong or something... Commented Nov 6, 2011 at 17:33
  • Well I don't know; I've never tried to use it. I'll see if I can make the promise thing work in your fiddle. (One slightly odd thing is that the "ajaxStop" event is triggered differently than the others, in the jQuery source code.) Commented Nov 6, 2011 at 17:34
  • It worked in 1.2.6 jsfiddle.net/etGPc/4. Commented Nov 6, 2011 at 18:09
  • 1
    @john_doe: Perhaps that's because of the ajaxPrefilter with scripts which are cross-domain (the scenario here) - see github.com/jquery/jquery/blob/master/src/ajax/script.js#L20. Commented Nov 6, 2011 at 18:16

3 Answers 3

3

I digged a little more into this issue and it's indeed the cross-domain script request that's the caveat. As I posted in the comments, that scenario has been implemented such that it sets the global option to false. This makes jQuery not to fire global ajax events. (No idea why that has been implemented though.)

This can be confirmed with this fiddle (pass means ajaxStop is fired):

  • cross-domain, no script: pass
  • cross domain, script: fail
  • no cross-domain, no script: pass
  • no cross-domain, script: pass

The most straight-forward thing to do is simply adding another prefilter which forces the global option to true:

jQuery.ajaxPrefilter( "script", function() {
    s.global = true;
});

This also makes this failing scenario pass in the fiddle.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for investigating this :)
2

you're doing it wrong anyways :)

here is one of the things that can happen: imagine the scripts are cached, then they might be loaded in no time. so, straight after the first call $.getScript( 'js/script1.js' ); the script will be available and $.ajaxStop (might!!!) get called, in the worst case that would happen three times.

to answer your question indirectly i would propose a different solution which avoids this race condition alltogether. you can try it here: http://jsfiddle.net/etGPc/8/

var urls, log, loaded;

// urls to load
urls = [
    'https://raw.github.com/h5bp/html5-boilerplate/master/js/script.js',
    'https://raw.github.com/h5bp/html5-boilerplate/master/js/plugins.js',
    'https://raw.github.com/h5bp/html5-boilerplate/master/js/libs/modernizr-2.0.6.min.js'
];

// urls loaded 
loaded = []; 


log = $( '#log' ); 

$.map( urls, function( url ){
    $.getScript( url, function(){
        // append to loaded urls
        loaded.push( url ); 
        log.append( "loaded " + url + "<br>" ); 

        // all loaded now? 
        if( loaded.length == urls.length ){
            log.append( "<b>all done!</b>" ); 
        }
    } ); 
} ); 

if you haven't seen jQuery.map before: it's not really different from a for-loop :)

another advantage here is that this method doesn't get confused if you have other ajax requests going on at the same time.

p.s. to avoid naming-clashes you can wrap the entire thing in a self-executing function, i.e.

function(){
    var urls, log, loaded; 

    ... all code here ... 
} (); 

Update: Refactored the code a bit...

var urls, loadedUrls, log;

urls = [
    'https://raw.github.com/h5bp/html5-boilerplate/master/js/script.js',
    'https://raw.github.com/h5bp/html5-boilerplate/master/js/plugins.js',
    'https://raw.github.com/h5bp/html5-boilerplate/master/js/libs/modernizr-2.0.6.min.js'
];    

loadedUrls = [];

log = $( '#log' )[0];

urls.forEach(function ( url ) {
    $.getScript( url, function () {
        loadedUrls.push( url );

        $( log ).append( 'loaded ' + url + '<br>' );

        if( loadedUrls.length === urls.length ){
            $( log ).append( '<b>all done!</b>' );
        }
    });
});

Live demo: http://jsfiddle.net/etGPc/10/

3 Comments

@kritzikratzi Yes, having a second array is a good solution. You don't need $.map though - invoking forEach on the urls array is sufficient.
@ŠimeVidas that's correct, i write lots of mathematica code where map and foreach are basically the same...
@kritzikratzi Well, each/forEach is the general iterator mechanism, whereas map specializes in replacing the values of the array.
2

While dealing with my own problem I found a much better and elegant solution to this problem, and it is using the jQuery deffered object. I think you already know about the function, maybe for another usecase, but it works great for loading files, and fire functions when eveything is done. Code is as follows:

function getLatestNews() {
    return $.get('/echo/js/?delay=2&js=', function(data) {
        console.log('news data received');
        $('.news').css({'color':'blue'});
    });
}

function getLatestReactions() {
    return $.get('/echo/js/?delay=5&js=', function(data) {
        console.log('reactions data received');
        $('.reactions').css({'color':'green'});
    });
}

function prepareInterface() {
    return $.Deferred(function(dfd) {
        var latest = $('.news, .reactions');
        latest.slideDown(500, dfd.resolve);
        latest.addClass('active');
    }).promise();
}

$.when(
    getLatestNews(), 
    getLatestReactions(), 
    prepareInterface()
).then(function() {
    console.log('fire after requests succeed');
    $('.finished').html('I am done!');
}).fail(function() {
    console.log('something went wrong!');
});

I made a small fiddle where you can check out the code.

http://jsfiddle.net/saifbechan/BKTwT/

You can check out a running copy there, I took the snippet from this tutorial, it's worth reading the whole tutorial, lot's of good information on asynchronous ajax.

http://msdn.microsoft.com/en-us/scriptjunkie/gg723713

3 Comments

Yes, the $.when().then() patterns is cool :) I've already made a solution with this pattern (see my last comment to my question above).
Oh I did not see that, it was hidden.
ah, i love this pattern! great solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.