JavaScript性能优化系列(八)弱网环境体验优化 - 8.2 请求优先级核心代码模板

弱网环境体验优化

8.2 请求优先级核心代码模板

一、核心代码模板

1. 请求优先级类型定义(TypeScript)

文件路径src/types/request.ts

/**
 * 请求优先级枚举
 * 用于约束所有请求的优先级层级
 */
export enum RequestPriority {
  HIGH = 'high',    // 核心请求:首屏关键数据、用户主动操作
  MEDIUM = 'medium', // 中等优先级:非核心但用户可见内容
  LOW = 'low'       // 低优先级:用户无感知、可延迟/降级内容
}

/**
 * 带优先级的请求配置接口
 * 扩展Axios请求配置,强制要求优先级参数
 */
import type { AxiosRequestConfig } from 'axios';

export interface PriorityAxiosRequestConfig extends AxiosRequestConfig {
  priority: RequestPriority; // 必传,请求优先级
  abortable?: boolean; // 可选,弱网下是否可中止(默认LOW可中止)
  timeout?: number; // 可选,超时时间(默认HIGH=5s, MEDIUM=8s, LOW=10s)
}
2. 请求优先级队列实现

文件路径src/utils/requestPriorityQueue.ts

import type { PriorityAxiosRequestConfig, RequestPriority } from '@/types/request';
import request from '@/utils/request'; // 后续配置的Axios实例
import { ref } from 'vue';

// 优先级权重映射(用于队列排序)
const PRIORITY_WEIGHT = {
  [RequestPriority.HIGH]: 3,
  [RequestPriority.MEDIUM]: 2,
  [RequestPriority.LOW]: 1
};

/**
 * 请求优先级队列类
 * 功能:按优先级调度请求、控制并发数、弱网下智能中止低优先级请求
 */
export class RequestPriorityQueue {
  private queue: [number, PriorityAxiosRequestConfig, AbortController][] = []; // 队列存储
  private currentConcurrency = 0; // 当前并发数
  private maxConcurrency = 3; // 弱网下默认最大并发数
  public isWeakNetwork = ref(false); // 网络状态标记

  constructor() {
    this.listenNetworkStatus(); // 初始化时监听网络状态
  }

  /** 监听网络状态变化(判断是否为弱网) */
  private listenNetworkStatus() {
    if (!navigator.connection) return;

    const updateStatus = () => {
      const isWeak = ['2g', 'slow-2g'].includes(navigator.connection.effectiveType);
      this.isWeakNetwork.value = isWeak;
      this.maxConcurrency = isWeak ? 3 : 5; // 弱网降低并发数
      if (isWeak) this.abortLowPriorityRequests(); // 弱网时中止低优先级请求
    };

    updateStatus(); // 初始检查
    navigator.connection.addEventListener('change', updateStatus); // 状态变化监听
  }

  /** 添加请求到队列并执行 */
  add<T = any>(config: PriorityAxiosRequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {
      // 补全默认配置
      const completeConfig = this.completeDefaultConfig(config);
      // 创建中止控制器
      const controller = new AbortController();
      completeConfig.signal = controller.signal;

      // 计算权重并加入队列
      const weight = PRIORITY_WEIGHT[completeConfig.priority];
      this.queue.push([weight, completeConfig, controller]);
      // 按权重排序(高优先级在前)
      this.queue.sort((a, b) => b[0] - a[0] || 0);

      // 执行队列
      this.runQueue(resolve, reject);
    });
  }

  /** 补全请求默认配置 */
  private completeDefaultConfig(config: PriorityAxiosRequestConfig): PriorityAxiosRequestConfig {
    return {
      timeout: config.timeout || {
        [RequestPriority.HIGH]: 5000,
        [RequestPriority.MEDIUM]: 8000,
        [RequestPriority.LOW]: 10000
      }[config.priority],
      abortable: config.abortable ?? config.priority === RequestPriority.LOW,
      ...config
    };
  }

  /** 执行队列(控制并发) */
  private async runQueue<T = any>(resolve: (data: T) => void, reject: (err: any) => void) {
    if (this.currentConcurrency >= this.maxConcurrency || this.queue.length === 0) return;

    // 取出队首请求(优先级最高)
    const [_, config, controller] = this.queue.shift()!;
    this.currentConcurrency++;

    try {
      // 发起请求
      const response = await request<T>(config);
      resolve(response.data);
    } catch (error: any) {
      // 核心请求失败重试1次(非中止错误)
      if (config.priority === RequestPriority.HIGH && !error.name.includes('AbortError')) {
        const retryRes = await request<T>(config);
        resolve(retryRes.data);
      } else {
        reject(error);
      }
    } finally {
      this.currentConcurrency--;
      this.runQueue(resolve, reject); // 继续执行队列
    }
  }

  /** 弱网时中止未执行的低优先级请求 */
  private abortLowPriorityRequests() {
    this.queue = this.queue.filter(([_, config, controller]) => {
      if (config.priority === RequestPriority.LOW && config.abortable) {
        controller.abort();
        return false;
      }
      return true;
    });
  }

  /** 清空队列(页面卸载时调用) */
  clear() {
    this.queue.forEach(([_, __, controller]) => controller.abort());
    this.queue = [];
    this.currentConcurrency = 0;
  }
}

// 全局单例队列实例
export const requestQueue = new RequestPriorityQueue();
3. Axios配置(注入优先级标记)

文件路径src/utils/request.ts

import axios from 'axios';
import type { PriorityAxiosRequestConfig, RequestPriority } from '@/types/request';

// 创建Axios实例
const instance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  headers: { 'Content-Type': 'application/json' }
});

// 请求拦截器:注入优先级标记
instance.interceptors.request.use(
  (config: PriorityAxiosRequestConfig) => {
    // 校验必传参数
    if (!config.priority) {
      throw new Error('请求必须指定priority(RequestPriority)');
    }

    // 添加优先级Header(前后端协同)
    config.headers['X-Request-Priority'] = config.priority;

    // 若使用Fetch适配器,传递priority属性(需安装axios-fetch-adapter)
    if (config.adapter === 'fetch') {
      (config as any).priority = config.priority;
    }

    // 添加Token(根据项目实际情况调整)
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    return config;
  },
  (error) => Promise.reject(error)
);

// 响应拦截器:处理中止错误
instance.interceptors.response.use(
  (response) => response,
  (error) => {
    // 过滤弱网下主动中止的低优先级请求错误
    if (error.name === 'AbortError') {
      console.log(`请求已中止(弱网优化): ${error.config?.url}`);
      return Promise.reject(new Error('请求已中止(弱网环境)'));
    }
    // 其他错误统一处理
    console.error('请求错误:', error);
    return Promise.reject(error);
  }
);

// 导出带优先级约束的请求方法
export default {
  get<T = any>(url: string, config: PriorityAxiosRequestConfig) {
    return instance.get<T>(url, config);
  },
  post<T = any>(url: string, data?: any, config: PriorityAxiosRequestConfig) {
    return instance.post<T>(url, data, config);
  },
  put<T = any>(url: string, data?: any, config: PriorityAxiosRequestConfig) {
    return instance.put<T>(url, data, config);
  },
  delete<T = any>(url: string, config: PriorityAxiosRequestConfig) {
    return instance.delete<T>(url, config);
  }
};
4. Vue3组件使用示例

文件路径src/views/HomePage.vue(首页示例)

<template>
  <div class="home-page">
    <!-- 核心内容区 -->
    <div class="core-content">
      <Skeleton v-if="!userInfo" />
      <UserInfoCard :info="userInfo" v-else />

      <Skeleton v-if="!goodsList" />
      <GoodsDisplay :list="goodsList" v-else />
    </div>

    <!-- 非核心内容区(延迟加载) -->
    <div class="non-core-content" v-if="showNonCore">
      <h3>推荐内容</h3>
      <RecommendList :data="recommendList" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref, nextTick } from 'vue';
import { requestQueue } from '@/utils/requestPriorityQueue';
import { RequestPriority } from '@/types/request';
import Skeleton from '@/components/Skeleton.vue';
import UserInfoCard from '@/components/UserInfoCard.vue';
import GoodsDisplay from '@/components/GoodsDisplay.vue';
import RecommendList from '@/components/RecommendList.vue';

// 状态定义
const userInfo = ref<any>(null);
const goodsList = ref<any[]>([]);
const recommendList = ref<any[]>([]);
const showNonCore = ref(false);

// 页面卸载时清空队列(避免内存泄漏)
onUnmounted(() => {
  requestQueue.clear();
});

onMounted(async () => {
  try {
    // 1. 高优先级:核心请求(并行发起)
    const [userRes, goodsRes] = await Promise.all([
      requestQueue.add({
        url: '/api/v1/user/current',
        method: 'get',
        priority: RequestPriority.HIGH
      }),
      requestQueue.add({
        url: '/api/v1/goods/list',
        method: 'get',
        priority: RequestPriority.HIGH
      })
    ]);
    userInfo.value = userRes;
    goodsList.value = goodsRes;

    // 2. 中等优先级:公告(核心内容加载后发起)
    const announcement = await requestQueue.add({
      url: '/api/v1/system/announcement',
      method: 'get',
      priority: RequestPriority.MEDIUM
    });
    // 处理公告逻辑...

    // 3. 低优先级:推荐列表(核心内容渲染后延迟发起)
    await nextTick(); // 等待核心内容DOM渲染完成
    showNonCore.value = true;
    recommendList.value = await requestQueue.add({
      url: '/api/v1/goods/recommend',
      method: 'get',
      priority: RequestPriority.LOW,
      abortable: true // 弱网下可中止
    });

    // 4. 低优先级:行为统计(用户无感知)
    await requestQueue.add({
      url: '/api/v1/stat/behavior',
      method: 'post',
      data: { page: 'home', action: 'loaded' },
      priority: RequestPriority.LOW,
      abortable: true
    });
  } catch (error) {
    console.error('首页加载失败:', error);
  }
});
</script>

<style scoped>
.core-content {
  margin-bottom: 20px;
}
.non-core-content {
  opacity: 0.9; /* 视觉上区分非核心内容 */
}
</style>

二、小剧场:团队如何复用代码模板

场景:小美按照模板修改完首页代码后,在团队周会上分享复用经验。

人物:小美(前端开发)、小稳(技术负责人)、小燕(质量工程师)、大熊(后端开发)

小美:“上周按请求优先级模板改了首页,现在弱网下用户信息和商品列表加载速度快了3秒!我分享下怎么复用模板吧:首先复制request.ts类型定义,然后是队列和Axios配置,最后在组件里用requestQueue.add发起请求,指定优先级就行。”

小稳:“嗯,模板里的队列单例很重要,全局只需要一个实例,不然并发控制会失效。你们注意到abortable参数了吗?非核心请求一定要设为true,弱网时队列会自动中止它们,释放资源给核心请求。”

小燕:“我测试的时候发现,弱网下推荐列表确实会被中止,控制台会打印‘请求已中止(弱网优化)’,这时候用户只能看到核心内容,体验反而更流畅了。不过有个问题:核心请求失败后重试,怎么验证重试逻辑生效?”

小美:“可以在DevTools的Network面板把核心接口设为‘Block’,然后看日志——模板里的队列会自动重试1次,失败才会抛错。我还加了个Skeleton组件,请求未完成时显示骨架屏,用户感知会更好。”

大熊:“后端已经支持X-Request-Priority头了,收到高优先级请求会放到优先队列处理。你们用模板的时候记得别改这个Header的key,不然后端识别不了。”

小稳:“另外,超核心的请求(比如登录状态)建议在index.html里加<link rel="preload">预加载,模板里的组件示例没写这个,需要的话可以参考之前的例子补充。还有页面卸载时的clear()方法一定要调用,避免切换路由后请求还在跑。”

小美:“对了,TypeScript的类型约束帮我发现了好几个没加priority的请求,编译时就报错了,比线上出问题再排查高效多了!”

小燕:“那我今天就用这个模板测一下商品详情页,重点看核心请求是不是真的优先响应,弱网并发数是不是控制在3个以内。”

小稳:“好,模板复用的时候有问题随时提,确保所有页面都按这个标准处理请求优先级,弱网体验就能统一优化了。”

复用说明

  1. 模板依赖axios,需确保项目已安装(npm install axios);
  2. 若需使用Fetch的priority属性,需安装Fetch适配器:npm install axios-fetch-adapter,并在请求配置中指定adapter: 'fetch'
  3. 网络状态监听依赖navigator.connection,低版本浏览器可能不支持,可根据项目兼容性需求补充polyfill;
  4. 优先级分类(哪些请求属于HIGH/LOW)需结合业务场景调整,模板仅提供基础框架。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值