实现带并发限制的 Promise 调度器

实现一个 Promise 调度器,要求:

  • 支持限制最大并发数 limit

  • 支持动态添加任务 add(taskFactory)

  • add 返回 Promise,在任务执行完成后 resolve

  • 调度器保证:

    • 如果当前运行数 < limit,立即执行
    • 否则进入队列,等待有任务结束再调度

典型应用:接口限流、分片上传、大规模爬虫请求

运行示例

js 复制代码
const scheduler = new Scheduler(2);

scheduler.add(() => timeout(1000).then(() => console.log('1')));
scheduler.add(() => timeout(500).then(() => console.log('2')));
scheduler.add(() => timeout(300).then(() => console.log('3')));
scheduler.add(() => timeout(400).then(() => console.log('4')));

// 输出顺序应为:2 3 1 4

基础版

最简单的想法:用一个 running 变量统计当前正在执行的任务数,如果 < limit 就执行,否则排队

ts 复制代码
class Scheduler {
    limit:number;
    running:0;
    queue:Array<()=>void>=[];
    
    constructor(limit:number){
        this.limit=limit
    }
    add (taskFactory:()=>Promise<any>):Promise<any>{
        return new Promise((resolve,reject)=>{
            const run = async ()=>{
                this.running++
                try{
                    const result=taskFactory()
                    resolve(result)
                } catch(err) {
                    reject(err)
                }  finally {
                    this.running--
                    this.next()
                }
            };
            
            if(this.running<this.limit){
                run()
            }else {
                this.queue.push(run)
            }
        })
    }
    
    private next(){
        if(this.running>=this.limit) return 
        const task=this.queue.shift()
        task && task()
    }
}

进阶版:并发调度+保序

在很多场景(如批量请求数据),我们需要 保持任务结果的顺序 。比如任务完成顺序是 B-C-A,但输出结果要保证是 [A, B, C]

思路:

  • add 时给每个任务分配一个索引
  • 用数组 results 按索引存储结果
  • 最终按原顺序返回结果
ts 复制代码
class SchedulerWithOrder {
  private limit: number;
  private running = 0;
  private queue: Array<() => void> = [];
  private results: any[] = [];

  constructor(limit: number) {
    this.limit = limit;
  }

  add<T>(taskFactory: () => Promise<T>, index: number): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      const run = async () => {
        this.running++;
        try {
          const result = await taskFactory();
          this.results[index] = result;
          resolve(result);
        } catch (err) {
          reject(err);
        } finally {
          this.running--;
          this.next();
        }
      };

      if (this.running < this.limit) {
        run();
      } else {
        this.queue.push(run);
      }
    });
  }

  private next() {
    if (this.running >= this.limit) return;
    const task = this.queue.shift();
    task && task();
  }

  getResults() {
    return this.results;
  }
}

高级版(超时/取消/错误策略)

实际工程中,还会有更多需求:

  • 任务超时:防止任务卡死
  • 任务取消 :类似 AbortController
  • 错误策略:失败是否中断?还是跳过继续?
ts 复制代码
class AdvancedScheduler {
  private limit: number;
  private running = 0;
  private queue: Array<() => void> = [];

  constructor(limit: number) {
    this.limit = limit;
  }
  
  add<T>(taskFactory:()=>Promise<T>,timeout=4000):Promise<T>{
    let timer: any;
    let rejectFn: (reason?: any) => void;

    const promise = new Promise<T>((resolve, reject) => {
      rejectFn = reject;
      const run = async () => {
        this.running++;
        try {
          const result = await Promise.race([
            taskFactory(),
            new Promise<T>((_, rej) => {
              timer = setTimeout(() => rej(new Error('Task timeout')), timeout);
            }),
          ]);
          resolve(result);
        } catch (err) {
          reject(err);
        } finally {
          clearTimeout(timer);
          this.running--;
          this.next();
        }
      };

      if (this.running < this.limit) {
        run();
      } else {
        this.queue.push(run);
      }
    });

    // 支持取消
    (promise as any).cancel = () => {
      rejectFn(new Error('Task cancelled'));
    };

    return promise;
  }
  
  private next(){
    if(this.running>=this.limit) return 
    const task=queue.shift()
    task && task()
  }
  
}

测试用例

ts 复制代码
const scheduler = new Scheduler(2);

const delay = (ms: number, label: string) => () =>
  new Promise(resolve => {
    setTimeout(() => {
      console.log(`Task ${label} done`);
      resolve(label);
    }, ms);
  });

async function test() {
  const results = await Promise.all([
    scheduler.add(delay(1000, 'A')),
    scheduler.add(delay(500, 'B')),
    scheduler.add(delay(300, 'C')),
    scheduler.add(delay(400, 'D')),
  ]);
  console.log('all done:', results);
}

test();
相关推荐
JELEE.22 分钟前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
电鱼智能的电小鱼1 小时前
基于电鱼 AI 工控机的智慧工地视频智能分析方案——边缘端AI检测,实现无人值守下的实时安全预警
网络·人工智能·嵌入式硬件·算法·安全·音视频
孫治AllenSun2 小时前
【算法】图相关算法和递归
windows·python·算法
TeleostNaCl2 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
格图素书3 小时前
数学建模算法案例精讲500篇-【数学建模】DBSCAN聚类算法
算法·数据挖掘·聚类
前端大卫4 小时前
为什么 React 中的 key 不能用索引?
前端
DashVector4 小时前
向量检索服务 DashVector产品计费
数据库·数据仓库·人工智能·算法·向量检索
AI纪元故事会4 小时前
【计算机视觉目标检测算法对比:R-CNN、YOLO与SSD全面解析】
人工智能·算法·目标检测·计算机视觉
你的人类朋友4 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
夏鹏今天学习了吗4 小时前
【LeetCode热题100(59/100)】分割回文串
算法·leetcode·深度优先