56

Is there an event that fires when JavaScript files are loaded? The problem came up because YSlow recommends to move JavaScript files to the bottom of the page. This means that $(document).ready(function1) is fired before the js file that contains the code for function1 is loaded.

How to avoid this kind of situation?

Sébastien Le Callonnec
  • 26,254
  • 8
  • 67
  • 80
Eugeniu Torica
  • 7,484
  • 12
  • 47
  • 62

7 Answers7

78

I don't have a reference for it handy, but script tags are processed in order, and so if you put your $(document).ready(function1) in a script tag after the script tags that define function1, etc., you should be good to go.

<script type='text/javascript' src='...'></script>
<script type='text/javascript' src='...'></script>
<script type='text/javascript'>
$(document).ready(function1);
</script>

Of course, another approach would be to ensure that you're using only one script tag, in total, by combining files as part of your build process. (Unless you're loading the other ones from a CDN somewhere.) That will also help improve the perceived speed of your page.

EDIT: Just realized that I didn't actually answer your question: I don't think there's a cross-browser event that's fired, no. There is if you work hard enough, see below. You can test for symbols and use setTimeout to reschedule:

<script type='text/javascript'>
function fireWhenReady() {
    if (typeof function1 != 'undefined') {
        function1();
    }
    else {
        setTimeout(fireWhenReady, 100);
    }
}
$(document).ready(fireWhenReady);
</script>

...but you shouldn't have to do that if you get your script tag order correct.


Update: You can get load notifications for script elements you add to the page dynamically if you like. To get broad browser support, you have to do two different things, but as a combined technique this works:

function loadScript(path, callback) {

    var done = false;
    var scr = document.createElement('script');

    scr.onload = handleLoad;
    scr.onreadystatechange = handleReadyStateChange;
    scr.onerror = handleError;
    scr.src = path;
    document.body.appendChild(scr);

    function handleLoad() {
        if (!done) {
            done = true;
            callback(path, "ok");
        }
    }

    function handleReadyStateChange() {
        var state;

        if (!done) {
            state = scr.readyState;
            if (state === "complete") {
                handleLoad();
            }
        }
    }
    function handleError() {
        if (!done) {
            done = true;
            callback(path, "error");
        }
    }
}

In my experience, error notification (onerror) is not 100% cross-browser reliable. Also note that some browsers will do both mechanisms, hence the done variable to avoid duplicate notifications.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks for your answer but for me it seems somehow strange why should I place $(document).ready(...) after – Eugeniu Torica Aug 20 '09 at 17:53
  • After? I'm not following you. – T.J. Crowder Aug 20 '09 at 20:19
  • The poster specifically asked because they moved JavaScript to the bottom of the `` (for better performance) -- this doesn't answer the question at all – philfreo May 18 '12 at 14:41
  • @philfreo: The second part comes close, and of course clearly the OP thought this answered the question (as the answer was accepted). But I can do better now, nearly two years later. :-) – T.J. Crowder May 18 '12 at 14:43
  • @philfreo: ...and now I have. :-) – T.J. Crowder May 18 '12 at 14:51
  • 1
    This is good, but if you're using jQuery, also consider `$.getScript()` http://api.jquery.com/jQuery.getScript/ – tybro0103 Oct 01 '13 at 18:20
  • 1
    For the dynamic loader, you forgot appendChild. Still awesome code though. – Eddie Oct 01 '15 at 15:38
  • @Eddie: Thanks, fixed. – T.J. Crowder Oct 01 '15 at 15:58
  • I always thought – Steverino Jun 21 '17 at 21:38
  • 1
    @Steverino: No, unless you use the `async` or `defer` attributes, which change things, `script` tags in the HTML bring the page loading to a screeching halt until that script code is run (because it may output things with `document.write`), so you know for sure that an earlier `script` has loaded (or failed) before a later one. (Browsers happily download the scripts in parallel, but they ensure they're executed in the order in the HTML.) `async` and `defer` change that. See: https://html.spec.whatwg.org/multipage/scripting.html#attr-script-async – T.J. Crowder Jun 22 '17 at 07:36
  • @T.J.Crowder Order of HTML and Script files: 1.HTML 2. `doc.ready` with a function definition having `console.log`. 3. `console.log`. What are the possible outcomes? 1.doc.ready file downloads first, registers and executes immediately before 2nd file because of no DOM 2.doc.ready file downloads first, registers and waits for the "DOWNLOAD" of 2nd file but "EXECUTES" before second file (is there a situation here where EITHER doc.ready's console OR second file's console can be printed - If so, if second file's console is printed first, then it may appear that doc.ready waits until 2nd's exec)... – user104309 Jul 19 '18 at 16:30
  • @user104309 - You'll need to post a real question with a [mcve] of the scenario. – T.J. Crowder Jul 19 '18 at 17:54
  • @T.J.Crowder Thanks. I will create one. – user104309 Jul 19 '18 at 18:11
6

When they say "The bottom of the page" they don't literally mean the bottom: they mean just before the closing </body> tag. Place your scripts there and they will be loaded before the DOMReady event; place them afterwards and the DOM will be ready before they are loaded (because it's complete when the closing </html> tag is parsed), which as you have found will not work.

If you're wondering how I know that this is what they mean: I have worked at Yahoo! and we put our scripts just before the </body> tag :-)

EDIT: also, see T.J. Crowder's reply and make sure you have things in the correct order.

NickFitz
  • 34,537
  • 8
  • 43
  • 40
5

Take a look at jQuery's .load() http://api.jquery.com/load-event/

$('script').load(function () { }); 
DalSoft
  • 10,673
  • 3
  • 42
  • 55
3

Further to @T.J. Crowder 's answer, I've added a recursive outer loop that allows one to iterate through all the scripts in an array and then execute a function once all the scripts are loaded:

loadList([array of scripts], 0, function(){// do your post-scriptload stuff})

function loadList(list, i, callback)
{
    {
        loadScript(list[i], function()
        {
            if(i < list.length-1)
            {
                loadList(list, i+1, callback);  
            }
            else
            {
                callback();
            }
        })
    }
}

Of course you can make a wrapper to get rid of the '0' if you like:

function prettyLoadList(list, callback)
{
    loadList(list, 0, callback);
}

Nice work @T.J. Crowder - I was cringing at the 'just add a couple seconds delay before running the callback' I saw in other threads.

Randhir Rawatlal
  • 365
  • 1
  • 3
  • 13
2

I always make a call from the end of the JavaScript files for registering its loading and it used to work perfect for me for all the browsers.

Ex: I have an index.htm, Js1.js and Js2.js. I add the function IAmReady(Id) in index.htm header and call it with parameters 1 and 2 from the end of the files, Js1 and Js2 respectively. The IAmReady function will have a logic to run the boot code once it gets two calls (storing the the number of calls in a static/global variable) from the two js files.

Faiz
  • 5,331
  • 10
  • 45
  • 57
0

Change the loading order of your scripts so that function1 was defined before using it in ready callback.

Plus I always found it better to define ready callback as an anonymous method then named one.

RaYell
  • 69,610
  • 20
  • 126
  • 152
  • Well it is not possible to define it before because it is in javascript file. – Eugeniu Torica Aug 18 '09 at 12:02
  • You can include the file with `function1` definition first and then include another file that uses that function. The point is that you cannot just add script files in random order. – RaYell Aug 18 '09 at 12:20
0

Like T.J. wrote: the order is defined (at least it's sequential when your browser is about to execute any JavaScript, even if it may download the scripts in parallel somehow). However, as apparently you're having trouble, maybe you're using third-party JavaScript libraries that yield some 404 Not Found or timeout? If so, then read Best way to use Google’s hosted jQuery, but fall back to my hosted library on Google fail.

Community
  • 1
  • 1
Arjan
  • 22,808
  • 11
  • 61
  • 71