Promise.all 동시 실행 수량 제한

  • Promise.all에서 동시에 실행할 수량을 제한하고 싶습니다

  • Promise Pool 이런 라이브러리가 있어요
  • 이외에 100 병렬 동작으로 갑자기 100 처리를 시작하는 것이 아니라 100 단위로 조금씩 증가
  • 예를 들어, 최소 실행 간격
  • 을 설정합니다.

    이루어지다


    export class ConcurrencyLock {
      private readonly concurrency: number;
      private readonly interval: number;
    
      private running = 0;
      private waitingResolves: Array<() => void> = [];
      private lastRunAt: Date | null = null;
    
      constructor({
        concurrency,
        interval,
      }: {
        concurrency: number;
        interval?: number;
      }) {
        this.concurrency = concurrency;
        this.interval = interval ?? 0;
      }
    
      async run<T>(func: () => PromiseLike<T> | T): Promise<T> {
        await this.get(new Date());
        const result = await func();
        await this.release(new Date());
    
        return result;
      }
    
      private async get(calledAt: Date) {
        await new Promise<void>(resolve => {
          if (this.running >= this.concurrency) {
            this.waitingResolves.push(resolve);
            return;
          }
    
          this.running += 1;
          this.schedule(resolve, calledAt);
        });
      }
    
      private async release(calledAt: Date) {
        if (this.running === 0) {
          console.warn('ConcurrencyLock#release was called but has no runnings');
          return;
        }
    
        if (this.waitingResolves.length === 0) {
          this.running -= 1;
          return;
        }
    
        const popped = this.waitingResolves.shift();
        this.schedule(popped, calledAt);
      }
    
      private schedule(func: () => void, calledAt: Date) {
        const willRunAt = !this.lastRunAt
          ? calledAt
          : new Date(
              Math.max(
                calledAt.getTime(),
                this.lastRunAt.getTime() + this.interval,
              ),
            );
    
        this.lastRunAt = willRunAt;
    
        setTimeout(func, willRunAt.getTime() - calledAt.getTime());
      }
    }
    

    사용법


    const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
    
    // 最大 5 並行、最短でも 1 秒間隔で実行
    const lock = new ConcurrencyLock({ concurrency: 5, interval: 1000 });
    const numbers = [...new Array(10)].map((_, i) => i);
    const duration = 10000; // 一つの処理は 10 秒要する
    const startedAt = new Date();
    
    const processed = await Promise.all(
      numbers.map(async number =>
        lock.run(async () => {
          const elapsed = Math.round(
            (new Date().getTime() - startedAt.getTime()) / 1000,
          );
          console.log(`${number} started after ${elapsed}s.`);
          await sleep(duration);
          return number;
        }),
      ),
    );
    
    // 0 started after 0s.
    // 1 started after 1s.
    // 2 started after 2s.
    // 3 started after 3s.
    // 4 started after 4s.
    // 5 started after 10s.
    // 6 started after 11s.
    // 7 started after 12s.
    // 8 started after 13s.
    // 9 started after 14s.
    

    좋은 웹페이지 즐겨찾기