前端接口并发方案具体代码实现

 更新时间:2026年06月17日 11:00:01   作者:米西米西1  
在前端中经常会出现多接口并发的场景,而针对请求常规方式会一股脑发送给浏览器,浏览器中会对大量请求进行排队,但大量请求推送至浏览器就会占用内存,最后达到一定量级导致页面卡顿,这篇文章主要介绍了前端接口并发方案具体代码实现的相关资料,需要的朋友可以参考下

一、方案背景

在前端中经常会出现多接口并发的场景,而针对请求常规方式会一股脑发送给浏览器,浏览器中会对大量请求进行排队,但大量请求推送至浏览器就会占用内存,最后达到一定量级导致页面卡顿,甚至假死常规请求图如下

二、解决思路

针对此模式,我们可以创建如下序列,待发送请求池作为中间层,只作为载体,每次推送固定数量到浏览器,当浏览器主线程中存在接口执行完毕后,代表主线程中接口数量减少,在从待发送请求中取出新的接口放入主线程,从而实现控制主线程发送数量避免内存溢出

  • 创建待发送请求池
  • 控制最大并行请求数量
  • 指定接口缓存,如数据源,结合请求,响应拦截器进行处理

流程图如下

三、具体代码实现

概述

这是一个基于请求池(Request Pool)的并发控制方案,用于管理前端应用中的 HTTP 请求。通过双队列机制控制并发数量,避免同时发起过多请求导致的性能问题和浏览器限制。

核心特性

  • 并发控制:限制同时执行的请求数量(默认 20 个)
  • 双队列设计:待执行队列(pendingQueue)+ 执行中队列(runningQueue)
  • 请求取消:支持取消单个或全部请求,基于 AbortController
  • 缓存支持:可配置的请求结果缓存机制
  • 内存优化:请求完成后自动清理引用,防止内存泄漏
  • 非阻塞调度:使用 setTimeout(0) 让出主线程,避免阻塞 UI
  • 防重复调度:通过 isProcessing 标志位避免重复调度

架构设计

1. 状态管理

const poolState = {
  pendingQueue: [],           // 待执行队列
  runningQueue: [],           // 执行中队列
  cacheStore: new Map(),      // 缓存存储
  maxConcurrent: 20,          // 最大并发数
  maxPendingQueue: Infinity,  // 待执行队列最大长度
  isProcessing: false         // 防止重复调度标志位
};

2、核心机制

防重复调度

使用 isProcessing 标志位防止多次调用 scheduleExecute() 时重复创建定时器:

const scheduleExecute = () => {
  if (poolState.isProcessing) return;  // 已在处理中,直接返回
  
  poolState.isProcessing = true;
  setTimeout(() => {
    processQueue();
    poolState.isProcessing = false;  // 处理完成,重置标志
  }, 0);
};

批量处理

每次调度时,根据可用槽位批量启动请求:

const processQueue = () => {
  // 计算可用槽位
  const slots = poolState.maxConcurrent - poolState.runningQueue.length;
  const count = Math.min(slots, poolState.pendingQueue.length);
  
  // 批量启动
  for (let i = 0; i < count; i++) {
    const task = poolState.pendingQueue.shift();
    if (task) {
      poolState.runningQueue.push(task);
      executeRequest(task);
    }
  }
};

API 文档

addRequest(config, axiosInstance)

添加请求到队列

参数:

  • config (Object): axios 请求配置对象
  • axiosInstance (Function): axios 实例

返回:

  • Promise: 请求的 Promise 对象

示例:

import axios from 'axios';
import { addRequest } from '@/utils/requestPool';

const config = {
  method: 'get',
  url: '/api/users',
  params: { page: 1 }
};

addRequest(config, axios)
  .then(response => console.log(response.data))
  .catch(error => console.error(error));

setMaxConcurrent(max)

设置最大并发数

参数:

  • max (Number): 最大并发数

示例:

import { setMaxConcurrent } from '@/utils/requestPool';

// 设置最大并发数为 10
setMaxConcurrent(10);

clearAllRequests()

取消所有请求(包括待执行和执行中的请求)

返回:

  • Object: 取消的请求数量统计
    • pending (Number): 待执行队列中取消的数量
    • running (Number): 执行中队列中取消的数量

示例:

import { clearAllRequests } from '@/utils/requestPool';

const count = clearAllRequests();
console.log(`取消了 ${count.pending} 个待执行请求`);
console.log(`取消了 ${count.running} 个执行中请求`);

getPoolState()

获取请求池当前状态(用于调试和监控)

返回:

  • Object: 请求池状态信息

示例:

import { getPoolState } from '@/utils/requestPool';

const state = getPoolState();
console.log('待执行:', state.pendingCount);
console.log('执行中:', state.runningCount);
console.log('缓存数:', state.cacheCount);

getCacheKey(config)

生成缓存键(预留功能)

参数:

  • config (Object): axios 配置对象,需包含 cacheParams 字段

返回:

  • String | null: 缓存键,如果未配置 cacheParams 则返回 null

getCache(key) / setCache(key, data) / clearCache(key)

缓存操作方法(预留功能)

使用示例

1. 基础集成 - Axios 拦截器

在 axios 实例中集成请求池:

// src/utils/request.js
import axios from 'axios';
import { addRequest, clearAllRequests } from './requestPool';

const service = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
});

// 请求拦截器
service.interceptors.request.use(
  config => {
    // 标记使用请求池
    config.usePool = true;
    return config;
  },
  error => Promise.reject(error)
);

// 响应拦截器
service.interceptors.response.use(
  response => response.data,
  error => {
    console.error('请求失败:', error);
    return Promise.reject(error);
  }
);

// 重写 axios 请求方法,使用请求池
const originalRequest = service.request.bind(service);
service.request = function(config) {
  if (config.usePool !== false) {
    // 使用请求池
    return addRequest(config, originalRequest);
  }
  // 不使用请求池,直接请求
  return originalRequest(config);
};

export default service;
export { clearAllRequests };

2. API 调用示例

// src/api/user.js
import request from '@/utils/request';

// 获取用户列表
export const getUserList = (params) => {
  return request({
    url: '/api/users',
    method: 'get',
    params
  });
};

// 获取用户详情
export const getUserDetail = (id) => {
  return request({
    url: `/api/users/${id}`,
    method: 'get'
  });
};

// 创建用户
export const createUser = (data) => {
  return request({
    url: '/api/users',
    method: 'post',
    data
  });
};

3. 组件中使用

<template>
  <div>
    <button @click="loadData">加载数据</button>
    <button @click="cancelAll">取消所有请求</button>
    <div>待执行: {{ poolState.pendingCount }}</div>
    <div>执行中: {{ poolState.runningCount }}</div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { getUserList } from '@/api/user';
import { clearAllRequests, getPoolState } from '@/utils/requestPool';

const poolState = ref({
  pendingCount: 0,
  runningCount: 0
});

// 更新请求池状态
const updatePoolState = () => {
  const state = getPoolState();
  poolState.value = state;
};

// 加载数据
const loadData = async () => {
  try {
    // 同时发起 50 个请求,但只有 20 个会并发执行
    const promises = Array.from({ length: 50 }, (_, i) => 
      getUserList({ page: i + 1 })
    );
    
    // 定时更新状态
    const timer = setInterval(updatePoolState, 100);
    
    const results = await Promise.all(promises);
    clearInterval(timer);
    
    console.log('所有请求完成:', results.length);
  } catch (error) {
    console.error('请求失败:', error);
  }
};

// 取消所有请求
const cancelAll = () => {
  const count = clearAllRequests();
  console.log(`已取消 ${count.pending + count.running} 个请求`);
  updatePoolState();
};

onMounted(() => {
  updatePoolState();
});
</script>

4. 路由切换时取消请求

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import { clearAllRequests } from '@/utils/requestPool';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // 路由配置...
  ]
});

// 路由切换前取消所有请求
router.beforeEach((to, from, next) => {
  if (from.path !== '/') {
    const count = clearAllRequests();
    console.log(`路由切换,取消了 ${count.pending + count.running} 个请求`);
  }
  next();
});

export default router;

5. 批量请求示例

// 批量加载用户详情
async function loadUserDetails(userIds) {
  const promises = userIds.map(id => getUserDetail(id));
  
  try {
    const results = await Promise.all(promises);
    console.log('加载完成:', results);
    return results;
  } catch (error) {
    console.error('批量加载失败:', error);
    throw error;
  }
}

// 使用示例
const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
loadUserDetails(userIds);

6. 动态调整并发数

import { setMaxConcurrent, getPoolState } from '@/utils/requestPool';

// 根据网络状况动态调整
function adjustConcurrency() {
  const connection = navigator.connection;
  
  if (connection) {
    const effectiveType = connection.effectiveType;
    
    switch (effectiveType) {
      case 'slow-2g':
      case '2g':
        setMaxConcurrent(5);
        break;
      case '3g':
        setMaxConcurrent(10);
        break;
      case '4g':
      default:
        setMaxConcurrent(20);
        break;
    }
    
    console.log('网络类型:', effectiveType);
    console.log('当前并发数:', getPoolState().maxConcurrent);
  }
}

// 监听网络变化
if (navigator.connection) {
  navigator.connection.addEventListener('change', adjustConcurrency);
}

7. 请求优先级(扩展示例)

如果需要实现请求优先级,可以扩展 addRequest 方法:

// 扩展版本 - 支持优先级
export const addRequestWithPriority = (config, axiosInstance, priority = 0) => {
  return new Promise((resolve, reject) => {
    const abortController = new AbortController();
    
    const task = {
      config: { ...config, signal: abortController.signal },
      resolve,
      reject,
      axiosInstance,
      abortController,
      priority  // 添加优先级字段
    };
    
    // 根据优先级插入队列
    const index = poolState.pendingQueue.findIndex(t => t.priority < priority);
    if (index === -1) {
      poolState.pendingQueue.push(task);
    } else {
      poolState.pendingQueue.splice(index, 0, task);
    }
    
    scheduleExecute();
  });
};

// 使用示例
addRequestWithPriority(config, axios, 10);  // 高优先级
addRequestWithPriority(config, axios, 0);   // 普通优先级

性能优化要点

1. 内存管理

请求完成后自动清理任务对象的引用:

task.config = null;
task.resolve = null;
task.reject = null;
task.axiosInstance = null;
task.abortController = null;

2. 非阻塞调度

使用 setTimeout(0) 让出主线程:

setTimeout(() => {
  processQueue();
  poolState.isProcessing = false;
}, 0);

3. 批量处理

一次调度处理多个请求,减少调度次数:

const count = Math.min(slots, poolState.pendingQueue.length);
for (let i = 0; i < count; i++) {
  // 批量启动
}

注意事项

  1. 并发数设置:默认 20 个,可根据实际情况调整
  2. 请求取消:路由切换或组件卸载时记得取消请求
  3. 错误处理:已取消的请求不会触发 reject,避免重复处理
  4. 缓存功能:当前代码中已预留,可根据需要启用
  5. 浏览器限制:不同浏览器对同域名并发请求有限制(通常 6-8 个),但通过请求池可以更好地控制

总结 

到此这篇关于前端接口并发方案具体代码实现的文章就介绍到这了,更多相关前端接口并发内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • avalon js实现仿微博拖动图片排序

    avalon js实现仿微博拖动图片排序

    玩微博的朋友都上传过图像吧,当图片上传后用户是可以随意拖动图片的,调整图片的顺序,那么此功能是怎么实现的,下面小编通过此篇文章给大家详解基于avalon js实现仿微博拖动图片排序,需要的朋友可以参考下
    2015-08-08
  • Javascript 中创建自定义对象的方法汇总

    Javascript 中创建自定义对象的方法汇总

    这篇文章主要汇总介绍了Javascript 中创建自定义对象的方法,需要的朋友可以参考下
    2014-12-12
  • JavaScript实现文本转语音功能的完整步骤

    JavaScript实现文本转语音功能的完整步骤

    这篇文章主要介绍了如何使用JavaScript和WebSpeechAPI快速实现一个简单的文本转语音Web应用,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-02-02
  • 前端无接口实现Table导出Excel的两种方案

    前端无接口实现Table导出Excel的两种方案

    在日常开发中,表格数据导出Excel是高频需求,多数场景下依赖后端接口返回二进制文件实现下载,但当无后端接口支持时,前端也可通过纯前端方案完成导出,以下是两种实用方案的详细实现与对比,需要的朋友可以参考下
    2025-08-08
  • JS新手入门数组处理的实用方法汇总

    JS新手入门数组处理的实用方法汇总

    这篇文章主要给大家介绍了关于JS新手入门数组处理实用方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • 一文详解JavaScrip可迭代对象(Iterable)

    一文详解JavaScrip可迭代对象(Iterable)

    本文解释了ES6引入迭代器和可迭代对象的原因及使用方法,通过引入这两个概念,JavaScript 统一了数据访问接口,解决了遍历方式不统一和灵活性差的问题,文章详细介绍了核心概念、底层原理、实战案例、常见误区及最佳实践,帮助读者彻底理解可迭代对象,需要的朋友可以参考下
    2026-05-05
  • javascript新建标签,判断键盘输入,以及判断焦点(示例代码)

    javascript新建标签,判断键盘输入,以及判断焦点(示例代码)

    这篇文章主要介绍了javascript新建标签,判断键盘输入,以及判断焦点(示例代码)。需要的朋友可以过来参考下,希望对大家有所帮助
    2013-11-11
  • js正则表达式replace替换变量方法

    js正则表达式replace替换变量方法

    这篇文章主要介绍了js正则表达式/replace替换变量方法 ,最近项目任务繁重,更新博客会较慢,不过有时间希望可以把自己的积累分享出来,需要的朋友可以参考下
    2016-05-05
  • javascript 必知必会之closure

    javascript 必知必会之closure

    本系列博文主要谈一些在 javascript 使用中经常会混淆的高级应用,包括: prototype, closure, scope, this关键字. 对于一个需要提高自己javascript水平的程序员,这些都是必须要掌握的.
    2009-09-09
  • javascript 实现的完全兼容鼠标滚轴缩放图片的代码

    javascript 实现的完全兼容鼠标滚轴缩放图片的代码

    以前看到的都是用IE的zoom,所以非IE就不支持,昨天看到这个js中鼠标滚轮事件详解 ,于是完全兼容(IE6-8,FF,Chrome,Opera,Safari)的鼠标滚轴缩放图片效果今天就诞生了
    2010-02-02

最新评论