如何使用JavaScript检测页面上的404错误?

31

我有一个HTML页面,其中引用了多个JavaScript、CSS和图片文件。这些引用是动态注入的,用户可以手动复制HTML页面和支持文件到另一台机器。

如果缺少某些JS或CSS文件,在控制台中浏览器会给出警告。例如:

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

我需要以某种方式将这些错误报告回来,以便我可以在HTML页面的内联JavaScript中要求用户首先验证支持文件是否正确复制。

有一个window.onerror事件,它只通知我页面上存在JS错误,例如意外的语法错误,但是当出现404 Not Found错误时,该事件不会触发。我想在任何资源类型(包括CSS、JS和图像)出现此类情况时进行检查。

我不喜欢使用jQuery AJAX来验证文件是否存在-每次页面加载都需要进行I/O开销,代价昂贵。

错误报告必须包含丢失的文件名称,以便我可以检查文件是核心还是可选的。

有什么想法吗?


2
本地文件不会生成404错误,因为那是一个HTTP错误代码。需要HTTP服务器来发送诸如错误之类的HTTP内容。您想检查文件是否存在吗? - pimvdb
当HTML页面在浏览器中加载时,我需要进行验证。这个可能吗? - Santoo
我在这个问题上添加了赏金。一个通用的答案,可以解释一种捕捉这些错误的方法(特别是当页面在Web服务器上运行时),将是很棒的。 - Brad
如果你正在使用jQuery,在error函数内部使用$(this)将返回该元素的属性,也就是文件名。尝试在这种情况下使用console.log($(this))来查看所有内容。 - Patrick Webster
@Brad,你说得很有道理,但请看一下我对那个问题的回答。只有当实际错误状态存在时,onerror()才会起作用。我不确定HTTP2会有什么样的自定义内容,但目前如果一个图像返回200 OK,浏览器必须假定没有错误,并且您正在接收到精确请求的资源位置上的内容,该位置已知为该资源的规范位置。 - Patrick Webster
显示剩余4条评论
4个回答

66

要捕获页面上所有的 error 事件,您可以使用 addEventListener 并将 useCapture 参数设置为 true。之所以不使用 window.onerror 是因为它使用了冒泡事件阶段,而您想要捕获的 error 事件不会冒泡。

如果您在加载任何外部内容之前将以下脚本添加到 HTML 中,您就可以捕获所有的 error 事件,即使在离线加载时也是如此。

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

你可以通过e.target访问导致错误的元素。例如,如果你想知道哪个文件在标记上未加载成功,你可以使用e.target.src来获取未能加载的URL。

注意:这实际上不会检测错误代码,它只会检测图像是否加载失败,因为无论状态代码如何,它的行为都是相同的。根据你的设置,这可能已经足够了,但是例如如果返回了404和有效图像,它将不会触发错误事件。


我已经断断续续地寻找这个解决方案好几个月了。非常感谢。还有一种方法可以获取错误代码吗? - Victoria
@Victoria 不是来自错误事件或DOM节点。唯一的方法是使用AJAX对URL进行另一个请求,但只有在URL满足同源策略CORS时才可能实现。 - Alexander O'Mara
再次感谢您……一个人可能会花很长时间寻找不存在的答案! - Victoria
4
如果一个 <img> 元素返回了 404 错误和有效的图片,这个事件处理程序不会触发。此时 <img> 元素不会触发 onerror 事件,因此没有任何事件冒泡上去。 - mpoisot

4
你可以使用 onloadonerror 属性来检测错误。
例如,在加载以下 HTML 时,它会弹出警告 "error1" 和 "error2",你可以调用自己的函数,如 onerror(logError(this);) 并将它们记录在一个数组中,一旦页面完全加载后使用单个 Ajax 调用进行提交。
<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
我用纯JavaScript编写了下面的代码,测试过并且可以执行。 所有源代码(html,css和Javascript)+图像和示例字体在这里:github上的链接。 第一个代码块是一个具有特定文件扩展名方法的对象:htmlcss。第二个代码块将在下面解释,但以下是简短描述。 它实现以下功能:
  • 函数check_file接受两个参数:一个字符串路径和回调函数。
  • 获取给定路径的内容
  • 获取给定路径的文件扩展名(ext
  • 调用srcFrom [ext]对象方法返回由srchref等在字符串上下文中引用的相对路径数组。
  • 对路径数组中的每个路径进行同步调用
  • 在错误时停止并返回HTTP错误消息和存在问题的路径,因此您还可以将其用于其他问题,如403(禁止访问)等。
为方便起见,它解析为相对路径名,并不关心使用哪种协议(http或https,都可以)。 它还会在解析CSS后清理DOM。
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;
    }
};

这里是获取文件内容并根据文件扩展名调用上述对象方法的函数:

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);
}

要使用它,只需像这样调用它:
var uri = 'htm/stuff.html';    // html example

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

请随意评论和编辑,我匆忙完成了这个任务,所以可能不是很完美 :)

0

@alexander-omara提供了解决方案。

您甚至可以在许多文件中添加它,但窗口处理程序只能/应该添加一次。

我使用单例模式来实现这一点:

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;
     };
  }());

现在,无论在哪个上下文中调用它,您都可以随意调用它多次,因为处理程序仅附加一次
some_global_object.error();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接