I couldn't find much useful information on the subject so I decided to do some tests. I set up a Node server that would:
- Send the start of an html document
- Wait 5 seconds
- Send the rest of the html which includes an image
I then recorded the status of document.ready and DOMContentLoaded at each stage. My code:
var http = require('http');
var server = http.createServer(function(req, res) {
    // Send the first part of the html
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(
        '<!doctype html>' +
            '<html lang="en">' +
                '<head>' +
                    '<meta charset="utf-8">' +
                    '<meta http-equiv="x-ua-compatible" content="ie=edge">' +
                    '<title>JS Ready Test</title>' +
                    '<meta name="description" content="">' +
                    '<meta name="viewport" content="width=device-width, initial-scale=1">' +
                    '<script>' +
                        'console.log(document.readyState);' +
                        'document.onreadystatechange = function () {' +
                            'console.log(document.readyState);' +
                        '};' +
                        'document.addEventListener("DOMContentLoaded", function() {' +
                            'console.log("DOMContentLoaded");' +
                        '});' +
                    '</script>' +
                '</head>' +
                '<body>');
    // Send a bunch of blank spaces so that the browser will load the buffer, if the buffer is too small the browser will wait for more data
    var str = 'Start';
    for (var i = 0; i < 2000; i++){
      str += ' ';
    }
    res.write(str);
    // Wait 5 seconds and send the rest of the data
    setTimeout(function () {
        res.write('Finish<img src="https://upload.wikimedia.org/wikipedia/commons/3/3d/LARGE_elevation.jpg"></body></html>');
        res.end();
    }, 5000);
});
// Listen on port 3000
server.listen(3000);
Results from my tests
First Buffer
Chrome (v43) / FF (v39) / IE11: document.ready === 'loading'
IE9 / IE10: document.ready === 'interactive'
Final buffer
Chrome / FF / IE11: document.ready === 'interactive', DOMContentLoaded called
IE9 / IE10: No change in document.ready, DOMContentLoaded called
Sub-resources finish loading (in this case the image)
Chrome / FF / IE11: document.ready === 'complete'
IE9 / IE10: document.ready === 'complete'
As you can see IE9 & IE10 set document.ready === 'interactive' too early.
Some possible solutions
1. Ignore IE9 / IE10
if (document.readyState === 'interactive' || document.readyState === 'complete') {
    callback();
} else {
    document.addEventListener('DOMContentLoaded', callback);
}
2. Add the DOMContentLoaded in the <head> of your document outside of your async script. This ensures that it will be attached before it is called.
// In <head>
<script>
    var pageLoaded = false;
    document.addEventListener('DOMContentLoaded', function() {
        pageLoaded = true;
    });
</script>
// In script.js
if (pageLoaded) {
    callback();
} else  {
    document.addEventListener('DOMContentLoaded', callback);
}
3. Fallback to the load event on `window.
if (document.readyState === 'complete') {
    callback();
} else {
    // You would need to add a safety so that your functions don't get called twice
    document.addEventListener('DOMContentLoaded', callback);
    window.addEventListener( "load", callback);
}