How to detect on-page 404 errors using JavaScript?

2019-01-22 13:43发布

I have an HTML page where several JavaScript, CSS and images files are referenced. These references are dynamically injected and user can manually copy the HTML page and the support files to another machine.

If some JS or CSS are missing, the browser complains in the console. For example:

Error GET file:///E:/SSC_Temp/html_005/temp/Support/jquery.js

I need somehow these errors reported back to me on the inline JavaScript of the HTML page so I can ask user to first verify that support files are copied correctly.

There's the window.onerror event which just inform me that there's a JS error on the page such as an Unexpected Syntax error, but this doesn't fire in the event of a 404 Not Found error. I want to check for this condition in case of any resource type, including CSS, JS, and images.

I do not like to use jQuery AJAX to verify that file physically exists - the I/O overhead is expensive for every page load.

The error report has to contain the name of the file missing so I can check if the file is core or optional.

Any Ideas?

5条回答
Melony?
2楼-- · 2019-01-22 14:14

@alexander-omara gave the solution.

You can even add it in many files but the window handler can/should be added once.

I use the singleton pattern to achieve this:

some_global_object = {
  error: (function(){
     var activate = false;
     return function(enable){
        if(!activate){
           activate = true;
           window.addEventListener('error', function(e){
              // maybe extra code here...
              // if(e.target.custom_property)
              // ...
           }, true);
        }
        return activate;
     };
  }());

Now, from any context call it as many times you want as the handler is attached only once:

some_global_object.error();
查看更多
霸刀☆藐视天下
3楼-- · 2019-01-22 14:20

you can use the onload and onerror attributes to detect the error

for example upon loading the following html it gives alert error1 and error2 you can call your own function e.g onerror(logError(this);) and record them in an Array and once the page is fully loaded post is with single Ajax call.

<html>
    <head>
        <script src="file:///SSC_Temp/html_005/temp/Support/jquery.js" onerror="alert('error1');" onload="alert('load');" type="text/javascript" ></script>
    </head>
    <body>
        <script src="file:///SSC_Temp/html_005/temp/Support/jquery.js" onerror="alert('error2');" onload="alert('load');" type="text/javascript" ></script>
    </body>
</html>
查看更多
狗以群分
4楼-- · 2019-01-22 14:26

I've put together the code below in pure JavaScript, tested, and it works. All the source code (html, css, and Javascript) + images and example font is here: on github.

The first code block is an object with methods for specific file extensions: html and css. The second is explained below, but here is a short description.

It does the following:

  • the function check_file takes 2 arguments: a string path and a callback function.
  • gets the contents of given path
  • gets the file extension (ext) of the given path
  • calls the srcFrom [ext] object method that returns an array of relative paths that was referenced in the string context by src, href, etc.
  • makes a synchronous call to each of these paths in the paths array
  • halts on error, and returns the HTTP error message and the path that had a problem, so you can use it for other issues as well, like 403 (forbidden), etc.

For convenience, it resolves to relative path names and does not care about which protocol is used (http or https, either is fine). It also cleans up the DOM after parsing the CSS.

var srcFrom = // object
{
    html:function(str)
    {
        var prs = new DOMParser();
        var obj = prs.parseFromString(str, 'text/html');
        var rsl = [], nds;

        ['data', 'href', 'src'].forEach(function(atr)
        {
            nds = [].slice.call(obj.querySelectorAll('['+atr+']'));
            nds.forEach(function(nde)
            { rsl[rsl.length] = nde.getAttribute(atr); });
        });

        return rsl;
    },

    css:function(str)
    {
        var css = document.createElement('style');
        var rsl = [], nds, tmp;

        css.id = 'cssTest';
        css.innerHTML = str;
        document.head.appendChild(css);
        css = [].slice.call(document.styleSheets);

        for (var idx in css)
        {
            if (css[idx].ownerNode.id == 'cssTest')
            {
                [].slice.call(css[idx].cssRules).forEach(function(ssn)
                {
                    ['src', 'backgroundImage'].forEach(function(pty)
                    {
                        if (ssn.style[pty].length > 0)
                        {
                            tmp = ssn.style[pty].slice(4, -1);
                            tmp = tmp.split(window.location.pathname).join('');
                            tmp = tmp.split(window.location.origin).join('');
                            tmp = ((tmp[0] == '/') ? tmp.substr(1) : tmp);
                            rsl[rsl.length] = tmp;
                        }
                    });
                });

                break;
            }
        }

        css = document.getElementById('cssTest');
        css.parentNode.removeChild(css);
        return rsl;
    }
};

And here is the function that gets the file contents and calls the above object method according to the file extension:

function check_file(url, cbf)
{
    var xhr = new XMLHttpRequest();
    var uri = new XMLHttpRequest();

    xhr.open('GET', url, true);

    xhr.onload = function()
    {
        var ext = url.split('.').pop();
        var lst = srcFrom[ext](this.response);
        var rsl = [null, null], nds;

        var Break = {};

        try
        {
            lst.forEach(function(tgt)
            {
                uri.open('GET', tgt, false);
                uri.send(null);

                if (uri.statusText != 'OK')
                {
                    rsl = [uri.statusText, tgt];
                    throw Break;
                }
            });
        }
        catch(e){}

        cbf(rsl[0], rsl[1]);
    };

    xhr.send(null);
}

To use it, simply call it like this:

var uri = 'htm/stuff.html';    // html example

check_file(uri, function(err, pth)
{
    if (err)
    { document.write('Aw Snap! "'+pth+'" is missing !'); }
});

Please feel free to comment and edit as you wish, i did this is a hurry, so it may not be so pretty :)

查看更多
来,给爷笑一个
5楼-- · 2019-01-22 14:33

To capture all error events on the page, you can use addEventListener with the useCapture argument set to true. The reason window.onerror will not do this is because it uses the bubble event phase, and the error events you want to capture do not bubble.

If you add the following script to your HTML before you load any external content, you should be able to capture all the error events, even when loading offline.

<script type="text/javascript">
window.addEventListener('error', function(e) {
    console.log(e);
}, true);
</script>

You can access the element that caused the error through e.target. For example, if you want to know what file did not load on an img tag, you can use e.target.src to get the URL that failed to load.

NOTE: This technically will not detect the error code, but if the image failed to load, is it technically behaves the same regardless of the status code. Depending on your setup this would probably be enough, but for example if a 404 is returned with a valid image it will not trigger an error event.

查看更多
beautiful°
6楼-- · 2019-01-22 14:33

You can use XMLHttpRequest for files.

var oReq = new XMLHttpRequest();
oReq.addEventListener("error", transferFailed, false);

function transferFailed(evt) {
  alert("An error occurred while transferring the file.");
}

client.open("GET", "unicorn.xml");
client.send();

and use the Image class for images.

var img1 = new Image();
img1.src = 'http://yourdomain.net/images/onethatdoesnotexist.gif';
img1.onerror = function () { alert( 'Image not loaded' ); };
查看更多
登录 后发表回答