在网络请求的生命周期中,精确地统计请求耗时对于性能监控、瓶颈排查和用户体验优化至关重要。本文将介绍三种主流的实现方案,从简单直接到企业级实践,并提供经过优化的、可直接用于生产环境的优雅代码。

方案一:直接记录法(侵入式)

这是最直观的方法:在请求代码前后直接记录时间戳并计算差值。

import axios from 'axios';

async function directRequest() {
  const startTime = Date.now();
  try {
    // 模拟一个耗时约 500ms 的 GET 请求
    await axios.get('https://httpbin.org/delay/0.5');
    const endTime = Date.now();
    const totalTime = endTime - startTime;
    console.log(`[方案一] 请求成功, 耗时: ${totalTime}ms`);
  } catch (error) {
    const endTime = Date.now();
    const totalTime = endTime - startTime;
    // 即使失败也应记录耗时,这对于问题排查同样重要
    console.error(`[方案一] 请求失败, 耗时: ${totalTime}ms`, error);
  }
}

优点:

  • 简单直接:代码逻辑一目了然,没有额外的封装概念。

缺点:

  • 严重侵入业务代码:每个请求都需要重复添加计时逻辑,造成代码冗余。
  • 难以统一管理:如果未来需要将耗时上报到监控平台或转换特定格式,需要修改所有请求点。
  • 可维护性差:当项目中请求数量增多时,维护成本急剧上升。

适用场景:非常简单的、一次性的脚本或原型验证。

方案二:高阶函数封装(装饰器模式)

通过创建一个高阶函数(Higher-Order Function)来“包装”原始的请求函数。这个包装器负责添加计时逻辑,从而将核心业务与性能监控分离。

import axios from 'axios';

/**
 * 包装请求函数以统计耗时
 * @param {Function} requestFn - 原始的请求函数 (例如: axios.get)
 * @returns {Function} - 包装后的函数
 */
function wrapRequest(requestFn) {
  return async function (...args) {
    const startTime = Date.now();
    try {
      const result = await requestFn(...args);
      const endTime = Date.now();
      console.log(`[方案二] 请求成功, 耗时: ${endTime - startTime}ms`);
      return result;
    } catch (error) {
      const endTime = Date.now();
      // 建议将耗时信息附加到error对象上,便于上层调用者捕获和分析
      error.duration = endTime - startTime;
      console.error(`[方案二] 请求失败, 耗时: ${endTime - startTime}ms`);
      throw error; // 重新抛出错误,保持原始行为
    }
  };
}

// 使用
const wrappedGet = wrapRequest(axios.get);
const wrappedPost = wrapRequest(axios.post);

async function wrappedRequestExample() {
  try {
    await wrappedGet('https://httpbin.org/delay/0.5');
    // await wrappedPost('https://httpbin.org/post', { data: 'test' });
  } catch (error) {
    // 此处仍能捕获到原始错误,如果需要耗时信息,可从error.duration获取
    // console.log('捕获到错误,耗时:', error.duration);
  }
}

优点:

  • 关注点分离:将计时逻辑与业务请求解耦,代码更清晰。
  • 高复用性:一次封装,随处可用,轻松应用到任何请求函数上。
  • 非侵入式:无需修改原始的业务请求代码。

缺点:

  • 需要主动去“包装”每个请求函数,虽然比方案一好,但仍然需要一些手动操作。

适用场景:中小型项目,或不使用特定HTTP库(如Axios)的场景,追求简单灵活的解决方案。

方案三:HTTP库拦截器(企业级实践)

对于Axios这类成熟的HTTP库,其内置的拦截器(Interceptors) 是处理此类横切关注点(Cross-Cutting Concerns)的完美工具。它能实现全局、自动化的请求耗时统计。

import axios from 'axios';

// 1. 扩展 axios 配置类型
declare module 'axios' {
  export interface InternalAxiosRequestConfig {
    startTime?: number;
  }
}

const service = axios.create({
  baseURL: 'https://httpbin.org',
  timeout: 5000,
})

// 2. 请求拦截器:记录开始时间
service.interceptors.request.use(
  (config) => {
    config.startTime = Date.now();
    return config;
  },
  (error) => Promise.reject(error)
);

// 3. 响应拦截器: 计算耗时并处理成功/失败逻辑
service.interceptors.response.use(
  (response) => {
    const { config, status } = response;
    const totalTime = Date.now() - (config.startTime ?? Date.now());
    console.log(`[方案三] 响应成功 [${status}], 耗时: ${totalTime}ms`);
    return response;
  },
  (error) => {
    // 即使是网络错误或超时,只要请求发出去了,同样可以统计耗时
    if (error.config) {
      const totalTime = Date.now() - (error.config.startTime ?? Date.now());
      console.error(`[方案三] 响应失败 [${error.response?.status || 'NETWORK_ERROR'}], 耗时: ${totalTime}ms`);
    }
    return Promise.reject(error);
  }
);

// 使用:所有通过此 `service` 实例发起的请求都会自动统计耗时
async function interceptorRequestExample() {
  try {
    // 自动触发拦截器,无需额外代码
    await service.get('/delay/0.5');
    // 模拟一个失败请求
    // await service.get('/status/500');
  } catch (e) {
    // 错误同样被拦截器处理过了
  }
}

优点:

  • 完全自动化:一次配置,全局生效,所有请求(包括成功和失败)都会被自动统计。
  • 零侵入业务代码:业务层完全无需关心计时逻辑。
  • 功能强大:除了计时,拦截器还可用于统一处理认证、日志、数据格式化等。

缺点:

  • 强依赖库:此方案与Axios等支持拦截器的库深度绑定。
  • 配置相对复杂,需要对HTTP库有一定了解。

适用场景:使用Axios、Fetch(可通过类似fetch-wrapper实现)等库进行统一网络请求管理的项目,是企业级应用的最佳选择。

总结与选择

特性方案一:直接记录方案二:高阶函数封装方案三:拦截器
侵入性,业务代码耦合度高,逻辑分离,完全分离
复用性极高,全局生效
工作量每个请求都需要一次封装,多处调用一次配置,全局生效
代码优雅度
适用场景一次性脚本、原型中小型项目、无统一HTTP库大型项目、使用Axios等库

最终建议:

  • 首选方案三(拦截器):如果你的项目已经使用了Axios,这是最优雅、最彻底的解决方案。
  • 次选方案二(高阶函数):如果你的项目不依赖特定HTTP库,或者请求方式多样,这是一个非常灵活且干净的折中方案。
  • 避免方案一:除非是极其简单的临时需求,否则应尽量避免使用这种方式,因为它会严重影响代码的长期可维护性。
Last modification:January 8, 2026
If you think my article is useful to you, please feel free to appreciate