如何检测 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() 停止观察
相关推荐
AlienZHOU7 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc10 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师11 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen12 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒12 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow12 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林81812 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang8007812 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明13 小时前
第 6 章 组件进阶
前端·vue.js
江华森13 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端