Как определить, не удалось ли загрузить тег <script>

Я динамически добавляю <script> теги к странице <head>, и я хотел бы иметь возможность сказать, произошла ли загрузка каким-либо образом - 404, ошибка скрипта в загруженном скрипте, что угодно.

В Firefox это работает:

var script_tag = document.createElement('script');
script_tag.setAttribute('type', 'text/javascript');
script_tag.setAttribute('src', 'http://fail.org/nonexistant.js');
script_tag.onerror = function() { alert("Loading failed!"); }
document.getElementsByTagName('head')[0].appendChild(script_tag);

Тем не менее, это не работает в IE или Safari.

Кто-нибудь знает способ заставить это работать в браузерах, отличных от Firefox?

(Я не думаю, что решение, которое требует размещения специального кода в файлах.js, является хорошим. Оно не элегантное и негибкое.)

16 ответов

Если вы заботитесь только о html5-браузерах, вы можете использовать событие ошибки (поскольку это только для обработки ошибок, должно быть нормально поддерживать это только в браузерах следующего поколения для KISS IMHO).

Из спецификации:

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

~

Если загрузка привела к ошибке (например, ошибка DNS или ошибка HTTP 404) Выполнение блока сценария должно просто состоять из запуска простого события с именем error в элементе.

Это означает, что вам не нужно делать какие-либо подверженные ошибкам опросы и можете объединить их с атрибутом async и defer, чтобы убедиться, что скрипт не блокирует рендеринг страницы:

Атрибут defer может быть задан, даже если указан атрибут async, чтобы старые браузеры, поддерживающие только отсрочку (а не async), переключались на режим отсрочки вместо режима синхронной блокировки, который используется по умолчанию.

Подробнее на http://www.w3.org/TR/html5/scripting-1.html

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

<script type="text/javascript" onload="loaded=1" src="....js"></script>

Мое рабочее чистое решение (2017)

function loaderScript(scriptUrl){
   return new Promise(function (res, rej) {
    let script = document.createElement('script');
    script.src = scriptUrl;
    script.type = 'text/javascript';
    script.onError = rej;
    script.async = true;
    script.onload = res;
    script.addEventListener('error',rej);
    script.addEventListener('load',res);
    document.head.appendChild(script);
 })

}

Сценарий от Erwinus прекрасно работает, но не очень четко закодирован. Я взял на себя смелость очистить его и расшифровать, что он делал. Я сделал эти изменения:

  • Значимые имена переменных
  • Использование prototype,
  • require() использует переменную аргумента
  • нет alert() сообщения возвращаются по умолчанию
  • Исправлены некоторые синтаксические ошибки и проблемы с областью видимости, которые я получал

Еще раз спасибо Erwinus, сама функциональность на месте.

function ScriptLoader() {
}

ScriptLoader.prototype = {

    timer: function (times, // number of times to try
                     delay, // delay per try
                     delayMore, // extra delay per try (additional to delay)
                     test, // called each try, timer stops if this returns true
                     failure, // called on failure
                     result // used internally, shouldn't be passed
            ) {
        var me = this;
        if (times == -1 || times > 0) {
            setTimeout(function () {
                result = (test()) ? 1 : 0;
                me.timer((result) ? 0 : (times > 0) ? --times : times, delay + ((delayMore) ? delayMore : 0), delayMore, test, failure, result);
            }, (result || delay < 0) ? 0.1 : delay);
        } else if (typeof failure == 'function') {
            setTimeout(failure, 1);
        }
    },

    addEvent: function (el, eventName, eventFunc) {
        if (typeof el != 'object') {
            return false;
        }

        if (el.addEventListener) {
            el.addEventListener(eventName, eventFunc, false);
            return true;
        }

        if (el.attachEvent) {
            el.attachEvent("on" + eventName, eventFunc);
            return true;
        }

        return false;
    },

    // add script to dom
    require: function (url, args) {
        var me = this;
        args = args || {};

        var scriptTag = document.createElement('script');
        var headTag = document.getElementsByTagName('head')[0];
        if (!headTag) {
            return false;
        }

        setTimeout(function () {
            var f = (typeof args.success == 'function') ? args.success : function () {
            };
            args.failure = (typeof args.failure == 'function') ? args.failure : function () {
            };
            var fail = function () {
                if (!scriptTag.__es) {
                    scriptTag.__es = true;
                    scriptTag.id = 'failed';
                    args.failure(scriptTag);
                }
            };
            scriptTag.onload = function () {
                scriptTag.id = 'loaded';
                f(scriptTag);
            };
            scriptTag.type = 'text/javascript';
            scriptTag.async = (typeof args.async == 'boolean') ? args.async : false;
            scriptTag.charset = 'utf-8';
            me.__es = false;
            me.addEvent(scriptTag, 'error', fail); // when supported
            // when error event is not supported fall back to timer
            me.timer(15, 1000, 0, function () {
                return (scriptTag.id == 'loaded');
            }, function () {
                if (scriptTag.id != 'loaded') {
                    fail();
                }
            });
            scriptTag.src = url;
            setTimeout(function () {
                try {
                    headTag.appendChild(scriptTag);
                } catch (e) {
                    fail();
                }
            }, 1);
        }, (typeof args.delay == 'number') ? args.delay : 1);
        return true;
    }
};

$(document).ready(function () {
    var loader = new ScriptLoader();
    loader.require('resources/templates.js', {
        async: true, success: function () {
            alert('loaded');
        }, failure: function () {
            alert('NOT loaded');
        }
    });
});

Я знаю, что это старая тема, но у меня есть хорошее решение для вас (я думаю). Он скопирован из моего класса, который обрабатывает все вещи AJAX.

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

function jsLoader()
{
    var o = this;

    // simple unstopable repeat timer, when t=-1 means endless, when function f() returns true it can be stopped
    o.timer = function(t, i, d, f, fend, b)
    {
        if( t == -1 || t > 0 )
        {
            setTimeout(function() {
                b=(f()) ? 1 : 0;
                o.timer((b) ? 0 : (t>0) ? --t : t, i+((d) ? d : 0), d, f, fend,b );
            }, (b || i < 0) ? 0.1 : i);
        }
        else if(typeof fend == 'function')
        {
            setTimeout(fend, 1);
        }
    };

    o.addEvent = function(el, eventName, eventFunc)
    {
        if(typeof el != 'object')
        {
            return false;
        }

        if(el.addEventListener)
        {
            el.addEventListener (eventName, eventFunc, false);
            return true;
        }

        if(el.attachEvent)
        {
            el.attachEvent("on" + eventName, eventFunc);
            return true;
        }

        return false;
    };

    // add script to dom
    o.require = function(s, delay, baSync, fCallback, fErr)
    {
        var oo = document.createElement('script'),
        oHead = document.getElementsByTagName('head')[0];
        if(!oHead)
        {
            return false;
        }

        setTimeout( function() {
            var f = (typeof fCallback == 'function') ? fCallback : function(){};
            fErr = (typeof fErr == 'function') ? fErr : function(){
                alert('require: Cannot load resource -'+s);
            },
            fe = function(){
                if(!oo.__es)
                {
                    oo.__es = true;
                    oo.id = 'failed';
                    fErr(oo);
                }
            };
            oo.onload = function() {
                oo.id = 'loaded';
                f(oo);
            };
            oo.type = 'text/javascript';
            oo.async = (typeof baSync == 'boolean') ? baSync : false;
            oo.charset = 'utf-8';
            o.__es = false;
            o.addEvent( oo, 'error', fe ); // when supported

            // when error event is not supported fall back to timer
            o.timer(15, 1000, 0, function() {
                return (oo.id == 'loaded');
            }, function(){ 
                if(oo.id != 'loaded'){
                    fe();
                }
            });
            oo.src = s;
            setTimeout(function() {
                try{
                    oHead.appendChild(oo);
                }catch(e){
                    fe();
                }
            },1); 
        }, (typeof delay == 'number') ? delay : 1);  
        return true;
    };

}

$(document).ready( function()
{
    var ol = new jsLoader();
    ol.require('myscript.js', 800, true, function(){
        alert('loaded');
    }, function() {
        alert('NOT loaded');
    });
});

Чтобы проверить, если JavaScript в nonexistant.js не вернул ошибку вы должны добавить переменную внутри http://fail.org/nonexistant.js лайк var isExecuted = true; и затем проверьте, существует ли он, когда тег скрипта загружен.

Однако, если вы хотите проверить, что nonexistant.js вернулся без 404 (то есть он существует), вы можете попробовать с isLoaded переменная...

var isExecuted = false;
var isLoaded = false;
script_tag.onload = script_tag.onreadystatechange = function() {
    if(!this.readyState ||
        this.readyState == "loaded" || this.readyState == "complete") {
        // script successfully loaded
        isLoaded = true;

        if(isExecuted) // no error
    }
}

Это покроет оба случая.

Я надеюсь, что это не получит отрицательного ответа, потому что в особых обстоятельствах это самый надежный способ решения проблемы. Каждый раз, когда сервер позволяет вам получить ресурс Javascript с помощью CORS ( http://en.wikipedia.org/wiki/Cross-origin_resource_sharing), у вас есть богатый набор опций для этого.

Использование XMLHttpRequest для извлечения ресурсов будет работать во всех современных браузерах, включая IE. Поскольку вы хотите загрузить Javascript, у вас есть Javascript, доступный вам в первую очередь. Вы можете отслеживать прогресс, используя readyState ( http://en.wikipedia.org/wiki/XMLHttpRequest). Наконец, как только вы получите содержимое файла, вы можете выполнить его с помощью eval (). Да, я сказал eval - с точки зрения безопасности он ничем не отличается от обычной загрузки скрипта. На самом деле Джон Резиг предлагает подобную технику, чтобы иметь более приятные теги ( http://ejohn.org/blog/degrading-script-tags/).

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

CORS также предпочтительнее JSONP для загрузки скриптов ( http://en.wikipedia.org/wiki/XMLHttpRequest). Однако, если вы разрабатываете свои собственные сторонние виджеты для встраивания в другие сайты, вы должны загрузить файлы Javascript из своего собственного домена в свой собственный iframe (опять же, используя AJAX)

Короче:

  1. Попробуй посмотреть, можешь ли ты загрузить ресурс используя AJAX GET

  2. Используйте eval после его успешной загрузки

Чтобы улучшить это:

  1. Проверьте отправляемые заголовки контроля кэша

  2. Посмотрите, как иначе кэшировать содержимое в localStorage, если вам нужно

  3. Проверьте Resig "унижающий JavaScript" для более чистого кода

  4. Проверьте require.js

Этот трюк сработал для меня, хотя я признаю, что это, вероятно, не лучший способ решить эту проблему. Вместо того, чтобы попробовать это, вы должны увидеть, почему JavaScript не загружается. Попробуйте сохранить локальную копию сценария на своем сервере и т. Д. Или узнайте у стороннего поставщика, откуда вы пытаетесь загрузить сценарий.

В любом случае, вот обходной путь: 1) Инициализируйте переменную в false 2) Установите ее в true при загрузке javascript (используя атрибут onload) 3) проверьте, является ли переменная true или false после загрузки тела HTML

<html>
  <head>
    <script>
      var scriptLoaded = false;

      function checkScriptLoaded() {
        if (scriptLoaded) {
          // do something here
        } else {
          // do something else here!
        }
      }
    </script>
    <script src="http://some-external-script.js" onload="scriptLoaded=true;" />
  </head>
  <body onload="checkScriptLoaded()">
    <p>My Test Page!</p>
  </body>
</html>

Вот еще одно решение на основе JQuery без таймеров:

<script type="text/javascript">
function loadScript(url, onsuccess, onerror) {
$.get(url)
    .done(function() {
        // File/url exists
        console.log("JS Loader: file exists, executing $.getScript "+url)
        $.getScript(url, function() {
            if (onsuccess) {
                console.log("JS Loader: Ok, loaded. Calling onsuccess() for " + url);
                onsuccess();
                console.log("JS Loader: done with onsuccess() for " + url);
            } else {
                console.log("JS Loader: Ok, loaded, no onsuccess() callback " + url)
            }
        });
    }).fail(function() {
            // File/url does not exist
            if (onerror) {
                console.error("JS Loader: probably 404 not found. Not calling $.getScript. Calling onerror() for " + url);
                onerror();
                console.error("JS Loader: done with onerror() for " + url);
            } else {
                console.error("JS Loader: probably 404 not found. Not calling $.getScript. No onerror() callback " + url);
            }
    });
}
</script>

Спасибо: /questions/28117605/kak-proverit-suschestvuet-li-fajl-v-jquery-ili-chistom-javascript/28117607#28117607

Пример использования (оригинальный пример из документации getScript для JQuery):

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jQuery.getScript demo</title>
  <style>
  .block {
     background-color: blue;
     width: 150px;
     height: 70px;
     margin: 10px;
  }
  </style>
  <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
</head>
<body>

<button id="go">&raquo; Run</button>
<div class="block"></div>

<script>


function loadScript(url, onsuccess, onerror) {
$.get(url)
    .done(function() {
        // File/url exists
        console.log("JS Loader: file exists, executing $.getScript "+url)
        $.getScript(url, function() {
            if (onsuccess) {
                console.log("JS Loader: Ok, loaded. Calling onsuccess() for " + url);
                onsuccess();
                console.log("JS Loader: done with onsuccess() for " + url);
            } else {
                console.log("JS Loader: Ok, loaded, no onsuccess() callback " + url)
            }
        });
    }).fail(function() {
            // File/url does not exist
            if (onerror) {
                console.error("JS Loader: probably 404 not found. Not calling $.getScript. Calling onerror() for " + url);
                onerror();
                console.error("JS Loader: done with onerror() for " + url);
            } else {
                console.error("JS Loader: probably 404 not found. Not calling $.getScript. No onerror() callback " + url);
            }
    });
}


loadScript("https://raw.github.com/jquery/jquery-color/master/jquery.color.js", function() {
  console.log("loaded jquery-color");
  $( "#go" ).click(function() {
    $( ".block" )
      .animate({
        backgroundColor: "rgb(255, 180, 180)"
      }, 1000 )
      .delay( 500 )
      .animate({
        backgroundColor: "olive"
      }, 1000 )
      .delay( 500 )
      .animate({
        backgroundColor: "#00f"
      }, 1000 );
  });
}, function() { console.error("Cannot load jquery-color"); });


</script>
</body>
</html>

Это можно сделать безопасно, используя обещания

function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error("Script load error: " + src));

    document.head.append(script);
  });
}

и использовать как это

let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js");

promise.then(
  script => alert(`${script.src} is loaded!`),
  error => alert(`Error: ${error.message}`)
);

Событие ошибки

* Обновление за август 2017: onerror запускается Chrome и Firefox. onload запускается Internet Explorer. Edge не запускает ни ошибки, ни загрузки. Я бы не использовал этот метод, но он мог бы работать в некоторых случаях. Смотрите также

Ошибка не работает в IE

*

Определение и использование Событие onerror вызывается, если при загрузке внешнего файла (например, документа или изображения) возникает ошибка.

Совет: При использовании на аудио / видео носителях связанные события, которые происходят, когда есть какое-то нарушение в процессе загрузки носителя:

  • OnAbort
  • onemptied
  • onstalled
  • onsuspend

В HTML:

элемент onerror="myScript">

В JavaScript, используя метод addEventListener():

object.addEventListener ("ошибка", myScript);

Примечание. Метод addEventListener() не поддерживается в Internet Explorer 8 и более ранних версиях.

Пример Выполните JavaScript, если при загрузке изображения возникает ошибка:

img src = "image.gif" onerror = "myFunction ()">

Ну, единственный способ, которым я могу думать о том, чтобы делать все, что ты хочешь, довольно уродлив. Сначала выполните AJAX-вызов, чтобы получить содержимое файла Javascript. Когда это завершится, вы можете проверить код состояния, чтобы решить, было ли это успешно или нет. Затем возьмите responseText из объекта xhr и оберните его в try / catch, динамически создайте тег script, и для IE вы можете установить свойство text тега script для текста JS, во всех других браузерах вы сможете добавить текстовый узел с содержимым к тегу скрипта. Если есть какой-либо код, который ожидает, что тег script на самом деле содержит местоположение src файла, это не сработает, но в большинстве случаев это подойдет. Некрасиво, но сработает. Вы можете взглянуть на мой быстрый и грязный пример здесь: http://pastie.org/829775

Причина, по которой он не работает в Safari, заключается в том, что вы используете синтаксис атрибутов. Это будет работать нормально, хотя:

script_tag.addEventListener('error', function(){/*...*/}, true);

... кроме как в IE.

Если вы хотите проверить, успешно ли выполнен скрипт, просто установите переменную, используя этот скрипт, и проверьте, чтобы он был задан во внешнем коде.

Для этого не нужен jquery, не нужно загружать асинхронный скрипт, не нужен таймер и не устанавливать значение для загруженного скрипта. Я тестировал его в FF, Chrome и Safari.

<script>
        function loadScript(src) {
          return new Promise(function(resolve, reject) {

            let s = window.document.createElement("SCRIPT");

            s.onload = () => resolve(s);
            s.onerror = () => reject(new Error(src));
            s.src = src;
            // don't bounce to global handler on 404.
            s.addEventListener('error', function() {});
            window.document.head.append(s);
          }); 
        }   
                        
        let successCallback = (result) => {
          console.log(scriptUrl + " loaded.");
        }   
                        
        let failureCallback = (error) => {
          console.log("load failed: " + error.message);
        }   
                        
        loadScript(scriptUrl).then(successCallback, failureCallback);
</script>

Было предложено установить тайм-аут, а затем предположить сбой загрузки после тайм-аута.

setTimeout(fireCustomOnerror, 4000);

Проблема с этим подходом состоит в том, что предположение основано на случайности. После истечения времени ожидания запрос все еще находится на рассмотрении. Запрос на ожидающий сценарий может загружаться даже после того, как программист предположил, что загрузка не произойдет.

Если запрос может быть отменен, то программа может подождать некоторое время, а затем отменить запрос.

Вы можете включить функцию в файл сценария java, если это возможно, например check() {var report;report=True}, затем запустить эту функцию, если функция не запускается или не отвечает, возможно, сценарий не загружен, иначе я думаю, что сценарий не готов к использованию. Атрибут onload также полезен.

Вот как я использовал обещание для обнаружения ошибок загрузки, которые генерируются в объекте окна:

<script type='module'>
window.addEventListener('error', function(error) {
  let url = error.filename
  url = url.substring(0, (url.indexOf("#") == -1) ? url.length : url.indexOf("#"));
  url = url.substring(0, (url.indexOf("?") == -1) ? url.length : url.indexOf("?"));
  url = url.substring(url.lastIndexOf("/") + 1, url.length);
  window.scriptLoadReject && window.scriptLoadReject[url] && window.scriptLoadReject[url](error);
}, true);
window.boot=function boot() {
  const t=document.createElement('script');
  t.id='index.mjs';
  t.type='module';
  new Promise((resolve, reject) => {
    window.scriptLoadReject = window.scriptLoadReject || {};
    window.scriptLoadReject[t.id] = reject;
    t.addEventListener('error', reject);
    t.addEventListener('load', resolve); // Careful load is sometimes called even if errors prevent your script from running! This promise is only meant to catch errors while loading the file.
  }).catch((value) => {
    document.body.innerHTML='Error loading ' + t.id + '! Please reload this webpage.<br/>If this error persists, please try again later.<div><br/>' + t.id + ':' + value.lineno + ':' + value.colno + '<br/>' + (value && value.message);
  });
  t.src='./index.mjs'+'?'+new Date().getTime();
  document.head.appendChild(t);
};
</script>
<script nomodule>document.body.innerHTML='This website needs ES6 Modules!<br/>Please enable ES6 Modules and then reload this webpage.';</script>
</head>

<body onload="boot()" style="margin: 0;border: 0;padding: 0;text-align: center;">
  <noscript>This website needs JavaScript!<br/>Please enable JavaScript and then reload this webpage.</noscript>

Я не знаю, как заставить это работать, но если вы хотите загружать JavaScript динамически, я знаю, что загрузка JavaScript с помощью jQuery запустит скрипт или просто загрузит JavaScript с любыми функциями AJAX и eval(), это должно работать,

Другие вопросы по тегам