Async-loaded scripts with DOMContentLoaded or load event handlers not being called?

心已入冬 提交于 2019-11-28 04:31:16

By loading the script asynchronously, you are telling the browser that it can load that script independently of the other parts of the page. That means that the page may finish loading and may fire DOMContentLoaded BEFORE your script is loaded and before it registers for the event. If that happens, you will miss the event (it's already happened when you register for it).

In some browsers, you can test the document to see if it's already loaded. I haven't checked all the browser compatibility, but in Firefox 3.6+ (MDN doc), you can check:

if (document.readyState !== "loading")

to see if the document is already loaded. If it is, just do your business. If it's not, then install your event listener.

In fact, as a reference source and implementation idea, jQuery does this very same thing with it's .ready() method and it looks widely supported. jQuery has this code when .ready() is called that first checks to see if the document is already loaded. If so, it calls the ready function immediately rather than binding the event listener:

// Catch cases where $(document).ready() is called after the
// browser event has already occurred.
if ( document.readyState === "complete" ) {
    // Handle it asynchronously to allow scripts the opportunity to delay ready
    return setTimeout( jQuery.ready, 1 );
}
Manuel Solorzano

This is not the final answer but made me understand why is not correct using async with a script that need to modify DOM, so must wait to DOMContentLoaded event. Hope could be beneficial.

(Source: Running Your Code at the Right Time from kirupa.com)

Most vanilla JS Ready functions do NOT consider the scenario where the DOMContentLoaded handler is initiated after the document already has loaded - Which means the function will never run. This can happen if you use DOMContentLoaded within an async external script (<script async src="file.js"></script>).

The code below checks for DOMContentLoaded only if the document's readyState isn't already interactive or complete.

var DOMReady = function(callback) {
  document.readyState === "interactive" || document.readyState === "complete" ? callback() : document.addEventListener("DOMContentLoaded", callback());
};
DOMReady(function() {
  //DOM ready!
});

If you want to support IE aswell:

var DOMReady = function(callback) {
    if (document.readyState === "interactive" || document.readyState === "complete") {
        callback();
    } else if (document.addEventListener) {
        document.addEventListener('DOMContentLoaded', callback());
    } else if (document.attachEvent) {
        document.attachEvent('onreadystatechange', function() {
            if (document.readyState != 'loading') {
                callback();
            }
        });
    }
};

DOMReady(function() {
  // DOM ready!
});

One way around this is to use the load event on the window object.

This will happen later than DOMContentLoaded, but at least you don't have to worry about missing the event.

window.addEventListener("load", function () {
   console.log('window loaded');
});

If you really need to catch DOMContentLoaded event you can do use Promise object. Promise will get resolved even if it happened earlier:

HTMLDocument.prototype.ready = new Promise(function (resolve) {
if (document.readyState != "loading")
    return resolve();
else
    document.addEventListener("DOMContentLoaded", function () {
        return resolve();
    });
});

document.ready.then(function () {
    console.log("document ready");
});
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!