如何检测 Network 请求异常 - PerformanceObserver

背景

我们想实现一个 Tampermonkey 插件,当发现某个接口报错则使用替代接口。比如某个 emojisearch 网站接口挂了,我们想替换成 kimi 或 deepseek 的接口,但是我们没法修改这个网站的源码。

其次我们经常在 Chrome 控制台的 Network 面板看到接口报错 500,如何监听这类错误呢?

GET https://example.com/api/completion?query=3 500 (Internal Server Error)

如何实现?可以使用 PerformanceObserver

ts 复制代码
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    console.log('entry', entry)
    
    if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
      if (entry.responseStatus >= 400) {
        console.error('网络请求错误:', entry.name, '状态码:', entry.responseStatus);
      }
    }
  });
});

observer.observe({ type: 'resource', buffered: true });

注意点entry.initiatorType 会报 ts 错误,因为 entry 的类型被误认为是 PerformanceEntry 没有该字段,TS 无法做到如此精确的推导问题 github.com/microsoft/T... 。从逻辑上看 type: resource 则 entry 的类型为 PerformanceResourceTiming 则自然有 initiatorType

PerformanceResourceTiming 继承自 PerformanceEntry
我们可以通过 @ts-expect-error https://github.com/microsoft/TypeScript/issues/58644 或更好的方式采用 type assertion。

diff 复制代码
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
+   const entry = perEntry as PerformanceResourceTiming;
    console.log('entry', entry)
    
    if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
      if (entry.responseStatus >= 400) {
        console.error('网络请求错误:', entry.name, '状态码:', entry.responseStatus);
      }
    }
  });
});

observer.observe({ type: 'resource', buffered: true });

我们封装下"检测网络请求异常"的函数。

ts 复制代码
function observeNetworkError(when: (entry: PerformanceResourceTiming) => boolean, callback: (entry: PerformanceResourceTiming) => unknown) {
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((perfEntry) => {
        const entry = perfEntry as PerformanceResourceTiming;

        if (when(entry)) {
          callback(entry)
        }
      });
    });

    observer.observe({ type: 'resource', buffered: true });
    
    return () => {
      observer.disconnect()
    }
}

用法:

ts 复制代码
const doSthOnNetworkError = (entry: PerformanceResourceTiming) => {
  console.error('网络请求错误:', entry.name, '状态码:', entry.responseStatus);
}

const disconnect = observeNetworkError(({ initiatorType, responseStatus }) => {
  return (initiatorType === 'fetch' || initiatorType === 'xmlhttprequest') && responseStatus >= 400
}, doSthOnNetworkError)

// 当不需要监听的时候
disconnect()

代码解释

代码解释

创建一个 PerformanceObserver 来监控资源加载性能指标,重点解释下 typebufferedinitiatorType 参数的作用:

type: 'resource'

  • 含义:指定要观察的性能条目类型
  • 作用 :这里设置为 'resource' 表示观察所有资源加载的性能数据
  • 其他常见类型 ,每一种类型都对应一个 interface PerformanceXxxTiming,更多见 PerformanceEntry: entryType property - MDN
    • 'largest-contentful-paint' - interface LargestContentfulPaint
    • 'navigation' - 页面导航性能 PerformanceNavigationTiming
    • 'paint' - 绘制性能(如 FP、FCP)。描述从 render tree 到绘画在屏幕上的一个个像素 PerformancePaintTiming
    • 'longtask' - 长任务,阻塞 UI 进程超过 50ms 的任务 PerformanceLongTaskTiming
    • 'element' - 特定元素的性能 PerformanceElementTiming

buffered: true

  • 含义:是否处理缓冲区中的历史性能条目
  • 作用
    • 当设置为 true 时,会立即返回在调用 observe() 之前已经存在的性能条目
    • 当设置为 false 时,只观察调用 observe() 之后新产生的性能条目
  • 使用场景
    • 如果你希望在代码初始化时就能获取到页面加载早期的性能数据,应该设为 true
    • 如果只关心后续发生的性能事件,可以设为 false

initiatorType: fetch | xmlhttprequest

  • 含义PerformanceResourceTiming 对象中的一个属性,用于表示 资源是由谁/什么发起的请求(即资源的初始化器类型)。它可以帮助开发者分析页面资源加载的来源和依赖关系。
  • 作用
    • 筛选出通过 fetch 或 xmlhttprequest 发起的请求
  • 使用场景
    1. 性能优化分析
      • 可以统计哪些类型的资源加载最慢(例如 scriptcssimg)。
      • 检测是否有意外的 fetch/xmlhttprequest 请求影响性能。
    2. 错误监控
      • 结合 responseStatus(如我们代码所示),可以监控 AJAX/Fetch 请求是否失败(>= 400)。
initiatorType 的可能值及含义

以下是常见的 initiatorType 值及其对应的含义:

说明
"img" 资源由 <img> 标签加载(例如 <img src="image.jpg">
"script" 资源由 <script> 标签加载(例如 <script src="app.js">
"link" 资源由 <link> 标签加载(例如 <link rel="stylesheet" href="style.css">
"css" 资源由 CSS 规则加载(例如 @import "theme.css"background: url(...)
"xmlhttprequest" 资源由 XMLHttpRequest 请求加载(AJAX)
"fetch" 资源由 fetch() 请求加载
"iframe" 资源由 <iframe>src 加载(例如 <iframe src="page.html">
"navigation" 资源是页面本身(即 HTML 文档的加载)
"audio" / "video" 资源由 <audio><video> 标签加载
"beacon" 资源由 navigator.sendBeacon() 发送
"other" 其他未明确分类的请求

组合效果

{ type: 'resource', buffered: true } 表示:

  1. 观察所有资源加载的性能数据
  2. 立即获取已经存在的资源加载性能数据(而不仅仅是后续新发生的)

实际应用示例

javascript 复制代码
// 这样设置可以获取到页面加载初期所有资源的性能数据
observer.observe({ 
  type: 'resource', 
  buffered: true 
});

// 如果只想观察后续发生的资源加载
observer.observe({ 
  type: 'resource', 
  buffered: false 
});

注意事项

  1. buffered: true 可能会一次性返回大量历史数据,要注意处理性能
  2. 不是所有性能条目类型都支持 buffered 选项
  3. 使用完毕后应调用 observer.disconnect() 停止观察
相关推荐
菜鸟una5 分钟前
【taro3 + vue3 + webpack4】在微信小程序中的请求封装及使用
前端·vue.js·微信小程序·小程序·typescript·taro
hao_041315 分钟前
elpis-core: 基于 Koa 实现 web 服务引擎架构设计解析
前端
狂野小青年1 小时前
npm 报错 gyp verb `which` failed Error: not found: python2 解决方案
前端·npm·node.js
鲁鲁5171 小时前
Windows 环境下安装 Node 和 npm
前端·npm·node.js
跑调却靠谱1 小时前
elementUI调整滚动条高度后与固定列冲突问题解决
前端·vue.js·elementui
呵呵哒( ̄▽ ̄)"2 小时前
React - 编写选择礼物组件
前端·javascript·react.js
Coding的叶子2 小时前
React Flow 简介:构建交互式流程图的最佳工具
前端·react.js·流程图·fgai·react agent
apcipot_rain7 小时前
【应用密码学】实验五 公钥密码2——ECC
前端·数据库·python
ShallowLin7 小时前
vue3学习——组合式 API:生命周期钩子
前端·javascript·vue.js
Nejosi_念旧7 小时前
Vue API 、element-plus自动导入插件
前端·javascript·vue.js