手把手教你用Promise搭个请求收费站!再也不怕压垮服务器啦~

25 阅读2分钟

当100个请求同时喊"我要下载"时...

image.png

新手:"不就是批量下载嘛,for循环一把梭!"
后端:"求放过!我的小霸王服务器要冒烟了!"

这时候就需要我们的——Promise智能收费站(正经名字叫请求池)来安排得明明白白!


一、先看个作死案例

// 作死の100连发请求!
const urls = Array(100).fill('https://xxx.com/file_')

console.time('毁灭吧服务器')
urls.forEach((url, i) => {
  fetch(`${url}${i}`// 直接开炮!
})
console.timeEnd('毁灭吧服务器'// 预计耗时:≈服务器爆炸时间

这时候你的服务器状态be like:
🔥 CPU Usage: 100%
💥 Memory: 爆炸边缘
🚑 后端同事:正在提刀赶来...


二、收费站の工作原理

2.1 就像食堂打饭窗口

食堂打饭示意图

image.png

  • 6个打饭窗口(并发通道)
  • 排队队列(任务队列)
  • 大妈打菜手速(服务器处理能力)

2.2 核心代码三件套

// 收费站构造函数
function createRequestToll(workerNum = 6) {
  let activeWorkers = 0  // 正在干活的窗口
  const taskQueue = []   // 排队的同学们

  // 叫号机:有空位就叫下一位
  const callNext = () => {
    while (activeWorkers < workerNum && taskQueue.length) {
      activeWorkers++  // 开放新窗口
      const task = taskQueue.shift()  // 队列第一个幸运儿
      
      task()
        .finally(() => {
          activeWorkers-- // 这个窗口又空出来啦
          callNext()      // 继续叫号!
        })
    }
  }

  // 取号排队功能
  return (task) => {
    taskQueue.push(task)  // 先拿个号等着
    callNext()           // 试试看能不能插队
  }
}

三、实战!给下载狂魔们发号码牌

// 开6个下载窗口
const getDownloadNumber = createRequestToll(6// 模拟100个文件下载
Array(100).fill(null).forEach((_, i) => {
  getDownloadNumber(() => {
    return fetch(`https://download.com/file_${i}`)
      .then(res => res.blob())
      .then(blob => {
        console.log(`文件${i}下载完成!大小:${blob.size}字节`)
      })
  })
})

这时候的服务器状态:
😴 CPU Usage: 30%
📉 Memory: 平稳波动
👨💻 后端同事:"早该这么搞了!"


四、高级技巧:VIP快速通道

给紧急任务开绿灯的魔改版:

function createPriorityToll(workerNum) {
  // ...前面的基础代码不变...

  return {
    // 普通用户排队
    normal: (task) => {
      taskQueue.push(task)  // 队尾老实排队
      callNext()
    },
    // VIP插队
    vip: (task) => {
      taskQueue.unshift(task) // 直接队头插队!
      callNext()
    }
  }
}

// 使用示例
const { normal, vip } = createPriorityToll(6)

vip(() => fetch('重要配置文件')) // 老板的文件优先!
normal(() => fetch('普通图片'))   // 打工人慢慢等

五、遇到的那些坑

5.1 下载到一半断网咋办?

// 给请求套上复活甲
const resilientFetch = (url, retry = 3) => {
  return fetch(url).catch(err => {
    if (retry > 0) {
      console.log(`重试机会-1,剩余${retry}次`)
      return resilientFetch(url, retry - 1)
    }
    throw new Error('摆烂了,实在下不动')
  })
}

5.2 进度条怎么搞?

// 进度监听小技巧
const withProgress = (promise, callback) => {
  let progress = 0
  const fakeProgress = setInterval(() => {
    progress = Math.min(progress + Math.random()*1095// 假装在下载
    callback(progress)
  }, 500)
  
  return promise.finally(() => {
    clearInterval(fakeProgress)
    callback(100// 最后直接拉到100%
  })
}

六、课后作业(才不是)

[ ] 试试改成动态并发数(网速快时多开几个窗口)
[ ] 添加取消排队功能("不想下了!"按钮)
[ ] 做个炫酷的队列可视化界面

据说给本文点赞的程序员,写代码永远不会出现429错误~(疯狂暗示)😉