如何判断 <script> 标签是否加载失败

IT技术 javascript onerror script-tag
2021-01-13 05:36:29

我正在向<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 文件中放置特殊代码的解决方案不是一个好的解决方案。它既不优雅又不灵活。)

6个回答

脚本标签没有错误事件。你可以判断它什么时候成功,并假设它在超时后没有加载:

<script type="text/javascript" onload="loaded=1" src="....js"></script>
嗯,是的,如果文件加载并且文件本身有错误,但是如果文件没有提供,onload 将永远不会触发。
2021-03-21 05:36:29
即使出现 javascript 错误,“onload”侦听器也会被触发。
2021-03-22 05:36:29
但是超时必须由页面的编码器提供,对吗?所以编码器可能会为浏览器选择不同的超时时间,假设脚本加载超时,然后让它在稍后成功。或不?
2021-03-24 05:36:29
是的,是的,这就是为什么我要求更具体地说明他正在寻找什么样的错误。
2021-03-31 05:36:29
脚本标签有 onerror evnt。找不到资源时会触发。
2021-04-10 05:36:29

我的工作清洁解决方案(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);
 })

}

正如马丁指出的那样,使用如下:

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

或者

try{
 await loaderScript("myscript.js")
 console.log("loaded"); 
}catch{
 console.log("error");
}
使用方法如下: loaderScript("myscript.js").then(() => { console.log("loaded"); }).catch(() => { console.log("error"); });
2021-03-22 05:36:29

2021 年更新: 今天所有浏览器都支持onerror=""脚本标签,示例:

2010 年的原始评论:

如果你只关心 html5 浏览器,你可以使用 error 事件。

从规范:

如果 src 属性的值为空字符串或无法解析,则用户代理必须将任务排队以在元素上触发名为error的简单事件,并中止这些步骤。

(...)

如果加载导致错误(例如 DNS 错误或 HTTP 404 错误),则执行脚本块必须只包括在元素上触发一个名为 error 的简单事件。

这意味着您不必进行任何容易出错的轮询,并且可以将其与 async 和 defer 属性结合使用,以确保脚本不会阻止页面呈现:

即使指定了 async 属性,也可以指定 defer 属性,以导致仅支持 defer(而非异步)的旧版 Web 浏览器回退到 defer 行为,而不是默认的同步阻塞行为。

更多关于http://www.w3.org/TR/html5/scripting-1.html#script

@Rudey Uncaught SyntaxError: Unexpected token '<'(在最新的 Chrome 中)
2021-03-13 05:36:29
<script src="nonexistent.js" onerror="alert('error!')"></script> 小提琴
2021-03-22 05:36:29
@daleyjem 听起来像是您尝试在 JavaScript 中放置 HTML 标签。<script src="nonexistent.js" onerror="alert('error!')"></script>应该放在你的 HTML 文件中,而不是 JS。
2021-03-24 05:36:29
请举例?
2021-03-25 05:36:29
任何关于 JSONP 实现的想法,比如 jQuery 支持这个?我在这里读到的关于检测 JSONP 加载失败的所有内容都表示无法检测到,但超时可以是一种解决方法,并且在最新版本的 JSONP 中添加了超时。似乎这个答案提供了比超时更好的东西 - 对吗?
2021-04-03 05:36:29

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');
        }
    });
});
@ZathrusWriter 这可能是一个更好的主意,感谢您告诉我们!您可能会在此代码中向 url 添加一个随机整数,它会删除缓存。
2021-03-25 05:36:29
@ZathrusWriter,那么 jQuery 实现的工作原理是什么?
2021-03-29 05:36:29
是的,Aram,那肯定可以解决问题,但是它也会使浏览器的缓存无效,因此在这种情况下的开销可能并不值得;)
2021-04-04 05:36:29
@Pacerier 抱歉,我不是 jQuery 核心开发人员,所以我无法真正回答
2021-04-09 05:36:29
我被迫使用 jQuery 的$.getScript,因为这个函数对于 MSIE8- 中的缓存脚本确实失败了,不幸的是
2021-04-12 05:36:29

我知道这是一个旧线程,但我为您提供了一个很好的解决方案(我认为)。它是从我的一类中复制而来的,它处理所有 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');
    });
});
而且,它是一个跨浏览器的解决方案;-)
2021-03-16 05:36:29
... jQuery 是从哪里来的?
2021-03-18 05:36:29
@Erwinus 我没有检查标题,这只是我执行的快速跨浏览器检查,$.getScript总是运行没有问题,所以我坚持使用它......欢迎你自己尝试,我正在使用 W7 和XAMPP在这里
2021-03-20 05:36:29
无论这是否有效,阅读起来都非常痛苦 - 更不用说调试了。这是一些非常糟糕的格式和真正糟糕的编码风格。单独的变量命名是一团糟。
2021-03-23 05:36:29
如果您没有看到可读代码的value(并且您可以在不对实际执行的代码进行任何更改的情况下使其可读),那么我为每个必须与您和您的代码一起工作的人感到抱歉。
2021-03-30 05:36:29