实现带并发限制的 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();
相关推荐
熊猫片沃子3 小时前
浅谈Vue 响应式原理
前端
Hard but lovely3 小时前
编译后视角的运算(关于中,后缀(逆波兰)表达式的计算)
开发语言·c++·算法
货拉拉技术3 小时前
微前端中的错误堆栈问题探究
前端·javascript·vue.js
2zcode3 小时前
基于Matlab能带态密度的载流子迁移-发射过程数值模拟与光强依赖性分析
人工智能·算法·matlab
前端老鹰3 小时前
HTML `<datalist>`:原生下拉搜索框,无需 JS 也能实现联想功能
前端·css·html
南北是北北3 小时前
Android TexureView和SurfaceView
前端·面试
code_YuJun3 小时前
脚手架架构设计
前端
pepedd8643 小时前
WebAssembly简单入门
前端·webassembly·trae
Asmalin3 小时前
【代码随想录day 22】 力扣 40.组合总和II
java·算法·leetcode