# All files / common circuit-breaker.ts

100% Statements 81/81
100% Branches 24/24
100% Functions 10/10
100% Lines 81/81

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125

    1x
    78x
    78x
    78x
    78x

    78x
    78x
    78x
    78x

    78x
    78x
    78x
    78x
    78x
    78x

    78x

    119x
    7x
    7x
    7x
    7x
    7x
    119x
    119x

    78x
    25x
    25x

    78x
    69x

    69x
    2x
    2x
    2x

    67x
    67x
    5x
    5x
    69x
    62x
    62x
    62x
    69x

    78x
    5x
    3x
    3x
    1x
    1x
    1x
    1x
    1x
    5x

    2x
    2x
    5x

    78x
    63x
    63x

    63x
    20x
    20x
    20x
    20x
    63x

    78x
    3x
    3x
    3x
    3x
    3x
    78x

    1x
    4x
    4x

    4x
    4x
    4x
    4x
    4x
    4x
    4x
     /**
     * 熔断器 - 供应商级别的故障隔离
     *
     * 状态机: CLOSED → (N次连续失败) → OPEN → (冷却期过后) → HALF_OPEN → (成功) → CLOSED
     *
     * 使用方式:
     *   const breaker = new CircuitBreaker({ name: 'minimax', failureThreshold: 3, cooldownMs: 30000 });
     *   try {
     *     await breaker.call(async () => { ... });
     *   } catch (e) {
     *     // 熔断开启或调用失败
     *   }
     */

    export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';

    export interface CircuitBreakerConfig {
      name: string;
      failureThreshold?: number;  // 连续失败多少次后熔断，默认 3
      cooldownMs?: number;        // 熔断冷却时间，默认 30000ms
      successThreshold?: number;  // HALF_OPEN 状态下需要连续成功多少次才恢复，默认 2
    }

    export class CircuitBreaker {
      readonly name: string;
      private failureThreshold: number;
      private cooldownMs: number;
      private successThreshold: number;

      private state: CircuitState = 'CLOSED';
      private failureCount = 0;
      private successCount = 0;
      private openedAt = 0;

      constructor(config: CircuitBreakerConfig) {
        this.name = config.name;
        this.failureThreshold = config.failureThreshold ?? 3;
        this.cooldownMs = config.cooldownMs ?? 30000;
        this.successThreshold = config.successThreshold ?? 2;
      }

      getState(): CircuitState {
        // 如果 OPEN 且冷却期已过，自动进入 HALF_OPEN
        if (this.state === 'OPEN' && Date.now() - this.openedAt >= this.cooldownMs) {
          this.state = 'HALF_OPEN';
          this.failureCount = 0;
          this.successCount = 0;
          console.log(`[熔断器] ${this.name}: OPEN → HALF_OPEN（冷却期已过）`);
        }
        return this.state;
      }

      isOpen(): boolean {
        return this.getState() === 'OPEN';
      }

      /**
       * 执行受保护的操作
       * 返回操作成功的结果，抛出 CircuitBreakerOpenError 或原始错误
       */
      async call<T>(fn: () => Promise<T>): Promise<T> {
        const currentState = this.getState();

        if (currentState === 'OPEN') {
          const remainingMs = this.cooldownMs - (Date.now() - this.openedAt);
          throw new CircuitBreakerOpenError(this.name, remainingMs);
        }

        try {
          const result = await fn();
          this.onSuccess();
          return result;
        } catch (error) {
          this.onFailure();
          throw error;
        }
      }

      private onSuccess(): void {
        if (this.state === 'HALF_OPEN') {
          this.successCount++;
          if (this.successCount >= this.successThreshold) {
            this.state = 'CLOSED';
            this.failureCount = 0;
            this.successCount = 0;
            console.log(`[熔断器] ${this.name}: HALF_OPEN → CLOSED（恢复）`);
          }
        } else {
          // CLOSED 状态，重置失败计数
          this.failureCount = 0;
        }
      }

      private onFailure(): void {
        this.failureCount++;
        this.successCount = 0;

        if (this.state === 'HALF_OPEN' || (this.state === 'CLOSED' && this.failureCount >= this.failureThreshold)) {
          this.state = 'OPEN';
          this.openedAt = Date.now();
          console.log(`[熔断器] ${this.name}: → OPEN（连续失败 ${this.failureCount} 次，冷却 ${this.cooldownMs}ms）`);
        }
      }

      /** 手动重置 */
      reset(): void {
        this.state = 'CLOSED';
        this.failureCount = 0;
        this.successCount = 0;
        this.openedAt = 0;
      }
    }

    export class CircuitBreakerOpenError extends Error {
      public readonly breakerName: string;
      public readonly remainingMs: number;

      constructor(name: string, remainingMs: number) {
        super(`[${name}] 熔断器已开启，剩余冷却时间 ${Math.ceil(remainingMs / 1000)}s`);
        this.name = 'CircuitBreakerOpenError';
        this.breakerName = name;
        this.remainingMs = remainingMs;
      }
    }