onerror是HTML中唯一能在标签级捕获资源加载失败的原生钩子,必须为每个元素显式绑定、按pathname粒度控制重试次数,并在<head>顶部内联执行监听逻辑。

onerror 回调必须绑定到具体资源元素上
HTML 本身不提供重试能力,onerror 是唯一能在标签级捕获加载失败的原生钩子。但直接写 onerror="retry(this)" 容易出问题:如果多个 <img> 或 <script> 共享同一段内联脚本,this 指向可能错乱;更常见的是,动态插入的元素没绑回调就已触发失败。
正确做法是为每个资源显式绑定,并确保作用域隔离:
- 静态 HTML 中,给每个
<img>加data-id或id,例如<img src="logo.png" data-id="logo" onerror="handleImgError(this)"> - 动态创建时,用
addEventListener('error', handler)替代内联属性,避免字符串拼接和执行上下文丢失 - 不要在
onerror里直接改this.src——浏览器可能缓存 404 响应,需先设为空再赋值:this.src = ''; this.src = newUrl;
script 加载失败必须用捕获阶段全局监听
window.addEventListener('error', handler, true) 是唯一能捕获 script 加载失败的方式,因为该事件不冒泡,且只在捕获阶段抛出。放在 <body> 后或外部 JS 里就完全失效——前面的 <script> 失败时监听还没注册。
关键判断逻辑只有两条:
立即学习“前端免费学习笔记(深入)”;
e.target.tagName === 'SCRIPT'-
!(e instanceof ErrorEvent)(排除 JS 运行时错误)
其他字段都不可靠:e.filename 和 e.message 在跨域脚本中为空;e.target.src 可能是相对路径,要用 new URL(e.target.src, location.href) 标准化;重试前必须清空 e.target.src 再赋值,否则浏览器跳过请求。
重试逻辑不能脱离资源上下文独立存在
一个按钮点击后重试哪张图?哪个脚本?哪个接口?答案必须由 DOM 结构本身携带,而不是靠 JS 硬编码或全局变量推断。常见错误是把所有重试逻辑塞进一个函数,然后靠索引、顺序或 class 名匹配目标元素——一旦 DOM 顺序变化或 class 被复用,就重试错对象。
可靠方案是让资源和重试入口共享唯一标识:
- 给
<img>加data-retry-key="user-avatar",对应按钮也加相同属性 - fetch 请求失败后,在容器上设
data-status="error"并插入带data-target="user-list"的按钮 - 点击时通过
document.querySelector('[data-retry-key="' + key + '"]')精准定位,不依赖层级或 class 名
这种绑定关系必须在资源加载前就建立,不能等 onerror 触发后再动态生成按钮并猜测目标。
重试次数和退避策略必须按路径粒度控制
对同一个 URL 连续重试 10 次毫无意义,尤其当失败原因是 CDN 故障或 DNS 解析失败时。真正需要的是按路径(url.pathname)计数,而非整个 URL 或域名。
示例场景:访问 /static/app.abc123.js 失败,重试时切换备用 CDN,但若三次都失败,就该停手——再试只是浪费用户流量和服务器压力。
- 用
Map存储pathname → count,每次重试前检查是否超限(如 ≥3) - 退避时间用指数增长:
setTimeout(fn, Math.pow(2, count) * 1000) - 切换备用地址时,保留原协议、query 参数,仅替换 hostname 或 pathname 前缀
最容易被忽略的是:重试逻辑本身也是资源,它所在的脚本如果加载失败,整个机制就瘫痪。所以核心监听代码必须内联在 <head> 最顶部,且不能用 ES6+ 语法。



















