Как узнать, не загружен ли тег <script>

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

В 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, является хорошим. Оно неэлегантное и негибкое.)

Ответ 1

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

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

Ответ 2

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

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

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

~

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

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

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

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

Ответ 3

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

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

Еще раз спасибо Эрвинусу, сама функциональность находится на месте.

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

Ответ 4

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

Когда script не может быть загружен, он устанавливает обработчик ошибок, но когда обработчик ошибок не поддерживается, он возвращается к таймеру, который проверяет наличие ошибок на 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');
    });
});

Ответ 5

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

Однако, если вы хотите только проверить, что возвращаемый 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
    }
}

Это будет охватывать оба случая.

Ответ 6

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

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

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

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

Короче:

  • Попробуйте посмотреть, можно ли загрузить ресурс с помощью AJAX GET

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

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

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

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

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

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

Ответ 7

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

В любом случае, так обходной путь: 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>

Ответ 8

Вот еще одно решение на основе 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>

Благодаря: fooobar.com/questions/30403/...

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

<!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>

Ответ 9

мое рабочее чистое решение (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);
 })

}

Как указал Мартин, используется так:

loaderScript("myscript.js")
  .then(() => { console.log("loaded"); })
  .catch(() => { console.log("error"); });

Ответ 10

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

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

... кроме IE.

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

Ответ 11

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

Ответ 12

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

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

Ответ 13

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

setTimeout(fireCustomOnerror, 4000);

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

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

Ответ 14

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

Ответ 15

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

<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>

Ответ 16

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