Как обнаружить ошибки на странице 404 с помощью JavaScript?

У меня есть HTML-страница, на которую ссылаются несколько файлов JavaScript, CSS и изображений. Эти ссылки динамически вводятся, и пользователь может вручную скопировать HTML-страницу и файлы поддержки на другую машину.

Если некоторые JS или CSS отсутствуют, браузер жалуется на консоль. Например:

Ошибка GET файла:///E:/SSC_Temp/html_005/temp/Support/jquery.js

Мне нужно, чтобы эти ошибки сообщались мне во встроенном JavaScript HTML-странице, поэтому я могу попросить пользователя сначала проверить, что файлы поддержки скопированы правильно.

Здесь есть событие window.onerror, которое просто сообщает мне, что на странице есть ошибка JS, такая как ошибка неожиданного синтаксиса, но это не срабатывает в случае ошибки 404 Not Found. Я хочу проверить это условие в случае любого типа ресурса, включая CSS, JS и изображения.

Я не люблю использовать jQuery AJAX для проверки того, что файл физически существует - накладные расходы ввода-вывода дороги для каждой загрузки страницы.

В отчете об ошибке должно содержаться имя файла, поэтому я могу проверить, является ли файл ядром или необязательным.

Любые идеи?

Ответы

Ответ 1

Чтобы захватить все события 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. Например, если вы хотите узнать, какой файл не был загружен в тег img, вы можете использовать e.target.src, чтобы получить URL, который не удалось загрузить.

ПРИМЕЧАНИЕ. Технически это не приведет к обнаружению кода ошибки, а к обнаружению сбоя при загрузке изображения, поскольку оно ведет себя одинаково независимо от кода состояния. В зависимости от вашей настройки этого, вероятно, будет достаточно, но, например, если 404 возвращается с действительным изображением, это не вызовет событие ошибки.

Ответ 2

Я собрал код ниже в чистом JavaScript, протестирован, и он работает. Все исходные коды (html, css и Javascript) + изображения и пример шрифта находятся здесь: в github.

Первый блок кода - это объект с методами для определенных расширений файлов: html и css. Второе объясняется ниже, но вот краткое описание.

Он выполняет следующие действия:

  • функция check_file принимает 2 аргумента: строковый путь и функцию обратного вызова.
  • получает содержимое заданного пути
  • получает расширение файла (ext) заданного пути
  • вызывает метод объекта srcFrom [ext], который возвращает массив относительных путей, на которые ссылался в контексте строки, src, href и т.д.
  • выполняет синхронный вызов каждого из этих путей в массиве путей
  • останавливается при ошибке и возвращает сообщение об ошибке HTTP и путь, который имел проблему, поэтому вы можете использовать его и для других проблем, например, 403 (запрещено) и т.д.

Для удобства он разрешает относительные имена путей и не заботится о том, какой протокол используется (http или https, либо это нормально). Он также очищает DOM после разбора 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;
    }
};

И вот функция, которая получает содержимое файла и вызывает указанный выше метод объекта в соответствии с расширением файла:

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

Пожалуйста, не стесняйтесь комментировать и редактировать, как хотите, я сделал это спешить, так что это может быть не так красиво:)

Ответ 3

вы можете использовать атрибуты onload и onerror для обнаружения ошибки

например, при загрузке следующего 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

Вы можете использовать XMLHttpRequest для файлов.

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

и используйте класс изображения для изображений.

var img1 = new Image();
img1.src = 'http://yourdomain.net/images/onethatdoesnotexist.gif';
img1.onerror = function () { alert( 'Image not loaded' ); };

Ответ 5

@Александр-Омара дал решение.

Вы даже можете добавить его во многие файлы, но обработчик окна можно/нужно добавить один раз.

Я использую шаблон синглтона для достижения этой цели:

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