如何解决页面请求接口大规模并发?

一、典型并发场景

  1. 初始化加载场景
    页面启动时需同时请求用户信息、配置数据、多模块内容等,导致瞬间高并发请求‌
    示例:电商首页需加载用户登录态、推荐商品、广告位等10+接口

  2. 批量操作场景
    用户执行批量下载/删除操作时触发大量并行请求,如选中100个文件执行批量删除‌
    示例:管理后台批量删除500条订单记录

  3. 实时数据场景
    高频轮询或WebSocket推送引发持续并发,如股票行情、在线协作编辑的频繁状态同步‌

  4. 级联请求场景
    前序请求结果触发后续多个并行请求,如选择省份后需同时加载城市列表和区域统计数据‌

二、核心解决方案

场景类型推荐方案优势
后台管理系统表格传统分页方案明确的页码导航
社交媒体信息流无限滚动+IntersectionObserver自然浏览体验
电商商品列表虚拟滚动+图片懒加载高性能处理百万级数据
单页应用内容区块组件级懒加载按需加载提升首屏速度

(一)流量控制策略

  1. 并发队列控制

    function fetchData(id) { // 修改为接收id参数
        return new Promise(resolve => {
          const delay = Math.random() * 1000 + 500; // 随机延迟500-1500ms
          setTimeout(() => {
            resolve({ id, delay: delay.toFixed(0) + 'ms' });
          }, delay);
        });
      }
      
      class ConcurrencyController {
        constructor(max = 6) {
          this.taskQueue = [];
          this.activeCount = 0;
          this.maxConcurrency = max;
        }
      
        add(caller) {
          return new Promise((resolve, reject) => {
            const task = this._createTask(caller, resolve, reject);
            if (this.activeCount >= this.maxConcurrency) {
              this.taskQueue.push(task);
            } else {
              task();
            }
          });
        }
      
        _createTask(caller, resolve, reject) {
          return () => {
            this.activeCount++; // 正确位置:任务开始时立即增加计数
            caller()
              .then(resolve)
              .catch(reject)
              .finally(() => {
                this.activeCount--;
                if (this.taskQueue.length > 0) {
                  const nextTask = this.taskQueue.shift();
                  nextTask(); // 自动执行下一个任务
                }
              });
          };
        }
      }
      let controller=new ConcurrencyController(2)
      const tasks = [
        controller.add(() => fetchData(1)),
        controller.add(() => fetchData(2)),
        controller.add(() => fetchData(3)),
        controller.add(() => fetchData(4)),
        controller.add(() => fetchData(5)),
        controller.add(() => fetchData(6))
      ];
      Promise.all(tasks)
      .then(results => {
        console.log('最终完成顺序:', results);
        console.log('预期输出顺序: [1,2,3,4,5,6] 的实际完成顺序可能不同');
      });

    核心机制解析

// 简化后的代码逻辑
add(caller) {
  return new Promise((outerResolve, outerReject) => {
    const task = () => {
      caller()                    // 1. 执行原始异步任务
        .then(outerResolve)       // 2. 将结果传递给外层Promise
        .catch(outerReject)       // 3. 错误冒泡
    }
  })
}

值传递流程

步骤1:任务执行
caller() // 执行返回Promise的异步任务,例如:
         // fetchData("url") → Promise<{data:"模拟数据"}>

步骤2:结果传递 

.then(outerResolve)
// 等价于:
.then(result => outerResolve(result))
步骤3:最终结果
// add()返回的Promise将获得caller()的结果
controller.add(() => fetchData(url))
  .then(data => {
    // data = {data:"模拟数据"} 
  })

使用并发控制库

  • 采用第三方库如 p-limit 快速实现并发控制,避免重复造轮子‌38
  • 实现示例:
import pLimit from 'p-limit';  
const limit = pLimit(6);  
const tasks = urls.map(url => limit(() => fetch(url)));  

 防抖和节流

传统分页实现(页码)

let currentPage = 1
const pageSize = 20

// 核心加载函数
async function loadPage(page) {
  try {
    const res = await fetch(`/api/data?page=${page}&size=${pageSize}`)
    const data = await res.json()
    renderList(data.items)
    document.getElementById('pageNum').textContent = page
  } catch(err) {
    console.error('加载失败', err)
  }
}

// 事件绑定
document.getElementById('prev').addEventListener('click', () => {
  if(currentPage > 1) loadPage(--currentPage)
})

document.getElementById('next').addEventListener('click', () => {
  loadPage(++currentPage)
})

// 初始化加载
loadPage(1)

 无限滚动分页(懒加载)

let isLoading = false
let currentPage = 1
const threshold = 200 // 距离底部200px触发加载

window.addEventListener('scroll', () => {
  const { scrollTop, clientHeight, scrollHeight } = document.documentElement
  // 到达触发点且未处于加载状态
  if(scrollTop + clientHeight >= scrollHeight - threshold && !isLoading) {
    loadMoreData()
  }
})

async function loadMoreData() {
  isLoading = true
  try {
    const res = await fetch(`/api/data?page=${currentPage}`)
    const data = await res.json()
    appendItems(data.items)
    currentPage++
    if(data.hasMore) showLoadingTip()
  } catch(err) {
    console.error('加载失败', err)
  } finally {
    isLoading = false
  }
}

// 初始加载
loadMoreData()

三、组件级懒加载(React示例)

import { useState, useEffect } from 'react'

function LazyList() {
  const [data, setData] = useState([])
  const [page, setPage] = useState(1)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      if(entries.isIntersecting && !loading) {
        loadMore()
      }
    }, { threshold: 0.1 })

    observer.observe(document.querySelector('#sentinel'))
    return () => observer.disconnect()
  }, [])

  const loadMore = async () => {
    setLoading(true)
    try {
      const res = await fetch(`/api/data?page=${page}`)
      const newData = await res.json()
      setData(prev => [...prev, ...newData.items])
      setPage(p => p + 1)
    } finally {
      setLoading(false)
    }
  }

  return (
    <div>
      {data.map(item => <div key={item.id}>{item.name}</div>)}
      <div id="sentinel">
        {loading ? '加载中...' : '滚动加载更多'}
      </div>
    </div>
  )
}

 图片懒加载优化

<img data-src="real-image.jpg" class="lazy-img" />

<script>
// 原生JS实现
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if(entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src
      observer.unobserve(img)
    }
  })
}, { rootMargin: '200px' })

document.querySelectorAll('.lazy-img').forEach(img => observer.observe(img))
</script>

关键优化点说明

  1. 防抖处理‌ - 滚动事件需添加防抖避免高频触发
function debounce(fn, delay=200) {
  let timer
  return (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => fn(...args), delay)
  }
}

2‌.虚拟列表优化‌ - 超长列表建议使用虚拟滚动方案(如react-window库)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值