如何检测 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() 停止观察
相关推荐
天天码行空15 分钟前
UnoCSS原子CSS引擎-前端CSS救星
前端
1_2_3_15 分钟前
抛弃 if-else,让 JavaScript 代码更高效
前端
火星思想15 分钟前
再来看看「从输入 URL 到看到页面」的整个流程
前端·面试
张开心_kx15 分钟前
不要再代码中滥用 useCallback 和useMemo
前端·react.js
Silence_xl16 分钟前
Vue3面包屑效果
前端
奶茶鉴赏专家17 分钟前
🚀 从 Vite 到 Rsbuild:一次意想不到的构建性能飞跃
前端
AronTing18 分钟前
代理模式:控制对象访问的中间层设计
前端·面试
自己记录_理解更深刻18 分钟前
在window系统,安装了全局的pnpm,用trae打开项目pnpm不能使用
前端
超凌19 分钟前
vue2,webpack 老项目清除无用的文件
前端
H5开发新纪元19 分钟前
我是如何用Cursor在10分钟内实现项目管理Mock方案的
前端·vue.js