Как определить, не удалось ли загрузить тег <script> - PullRequest
102 голосов
/ 11 февраля 2009

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

Ответы [ 15 ]

32 голосов
/ 12 февраля 2009

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

<script type="text/javascript" onload="loaded=1" src="....js"></script>
23 голосов
/ 12 ноября 2010

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

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

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

~

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

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

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

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

20 голосов
/ 11 мая 2013

Сценарий от 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');
        }
    });
});
16 голосов
/ 05 августа 2011

Я знаю, что это старая ветка, но у меня есть хорошее решение для вас (я думаю). Он скопирован из моего класса, который обрабатывает все вещи 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');
    });
});
9 голосов
/ 12 февраля 2009

Чтобы проверить, не вернул ли 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
    }
}

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

7 голосов
/ 02 июня 2017

мой рабочий чистый раствор (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"); });

6 голосов
/ 01 февраля 2013

Надеюсь, это не получит отрицательного ответа, потому что в особых обстоятельствах это самый надежный способ решения проблемы. Каждый раз, когда сервер позволяет вам получить ресурс 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 - потому что с точки зрения безопасности он ничем не отличается от обычной загрузки скрипта. На самом деле Джон Резиг предлагает подобную технику, чтобы иметь более приятные теги (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)

Короче говоря:

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

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

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

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

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

  3. Посмотрите на "унизительный JavaScript" Ресига для более чистого кода

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

5 голосов
/ 12 января 2013

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

В любом случае, вот обходной путь: 1) Инициализировать переменную в false 2) Установите его в true при загрузке JavaScript (используя атрибут onload) 3) проверьте, является ли переменная истинной или ложной после загрузки тела 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>
4 голосов
/ 14 октября 2013

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

Благодаря: https://stackoverflow.com/a/14691735/1243926

Пример использования (исходный пример из документации по 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>
1 голос
/ 30 июля 2018

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

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