condition-based-waiting

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Condition-Based Waiting

基于条件的等待

Overview

概述

Flaky tests often guess at timing with arbitrary delays. This creates race conditions where tests pass on fast machines but fail under load or in CI.
Core principle: Wait for the actual condition you care about, not a guess about how long it takes.
不稳定测试通常会通过任意延迟来猜测时序。这会导致竞态条件,使得测试在快速机器上能通过,但在负载下或CI环境中失败。
核心原则: 等待你真正关心的实际条件,而非猜测所需时长。

When to Use

使用场景

dot
digraph when_to_use {
    "Test uses setTimeout/sleep?" [shape=diamond];
    "Testing timing behavior?" [shape=diamond];
    "Document WHY timeout needed" [shape=box];
    "Use condition-based waiting" [shape=box];

    "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
    "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
    "Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
}
Use when:
  • Tests have arbitrary delays (
    setTimeout
    ,
    sleep
    ,
    time.sleep()
    )
  • Tests are flaky (pass sometimes, fail under load)
  • Tests timeout when run in parallel
  • Waiting for async operations to complete
Don't use when:
  • Testing actual timing behavior (debounce, throttle intervals)
  • Always document WHY if using arbitrary timeout
dot
digraph when_to_use {
    "Test uses setTimeout/sleep?" [shape=diamond];
    "Testing timing behavior?" [shape=diamond];
    "Document WHY timeout needed" [shape=box];
    "Use condition-based waiting" [shape=box];

    "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
    "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
    "Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
}
适用场景:
  • 测试使用了任意延迟(
    setTimeout
    sleep
    time.sleep()
  • 测试结果不稳定(时而通过,负载下失败)
  • 并行运行时测试超时
  • 等待异步操作完成
不适用场景:
  • 测试实际时序行为(防抖、节流间隔)
  • 若必须使用任意超时,务必记录原因

Core Pattern

核心模式

typescript
// ❌ BEFORE: Guessing at timing
await new Promise(r => setTimeout(r, 50));
const result = getResult();
expect(result).toBeDefined();

// ✅ AFTER: Waiting for condition
await waitFor(() => getResult() !== undefined);
const result = getResult();
expect(result).toBeDefined();
typescript
// ❌ 之前:猜测时序
await new Promise(r => setTimeout(r, 50));
const result = getResult();
expect(result).toBeDefined();

// ✅ 之后:等待条件满足
await waitFor(() => getResult() !== undefined);
const result = getResult();
expect(result).toBeDefined();

Quick Patterns

快速模式参考

ScenarioPattern
Wait for event
waitFor(() => events.find(e => e.type === 'DONE'))
Wait for state
waitFor(() => machine.state === 'ready')
Wait for count
waitFor(() => items.length >= 5)
Wait for file
waitFor(() => fs.existsSync(path))
Complex condition
waitFor(() => obj.ready && obj.value > 10)
场景模式
等待事件
waitFor(() => events.find(e => e.type === 'DONE'))
等待状态
waitFor(() => machine.state === 'ready')
等待数量达标
waitFor(() => items.length >= 5)
等待文件生成
waitFor(() => fs.existsSync(path))
复杂条件
waitFor(() => obj.ready && obj.value > 10)

Implementation

实现方式

Generic polling function:
typescript
async function waitFor<T>(
  condition: () => T | undefined | null | false,
  description: string,
  timeoutMs = 5000
): Promise<T> {
  const startTime = Date.now();

  while (true) {
    const result = condition();
    if (result) return result;

    if (Date.now() - startTime > timeoutMs) {
      throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
    }

    await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
  }
}
See @example.ts for complete implementation with domain-specific helpers (
waitForEvent
,
waitForEventCount
,
waitForEventMatch
) from actual debugging session.
通用轮询函数:
typescript
async function waitFor<T>(
  condition: () => T | undefined | null | false,
  description: string,
  timeoutMs = 5000
): Promise<T> {
  const startTime = Date.now();

  while (true) {
    const result = condition();
    if (result) return result;

    if (Date.now() - startTime > timeoutMs) {
      throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
    }

    await new Promise(r => setTimeout(r, 10)); // 每10ms轮询一次
  }
}
查看@example.ts获取完整实现,包含实际调试会话中用到的领域特定辅助函数(
waitForEvent
waitForEventCount
waitForEventMatch
)。

Common Mistakes

常见错误

❌ Polling too fast:
setTimeout(check, 1)
- wastes CPU ✅ Fix: Poll every 10ms
❌ No timeout: Loop forever if condition never met ✅ Fix: Always include timeout with clear error
❌ Stale data: Cache state before loop ✅ Fix: Call getter inside loop for fresh data
❌ 轮询过于频繁:
setTimeout(check, 1)
- 浪费CPU ✅ 修复方案: 每10ms轮询一次
❌ 未设置超时: 若条件始终不满足会无限循环 ✅ 修复方案: 始终包含超时设置并给出清晰错误信息
❌ 数据过期: 在循环前缓存状态 ✅ 修复方案: 在循环内部调用获取函数以获取最新数据

When Arbitrary Timeout IS Correct

何时可以使用任意超时

typescript
// Tool ticks every 100ms - need 2 ticks to verify partial output
await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition
await new Promise(r => setTimeout(r, 200));   // Then: wait for timed behavior
// 200ms = 2 ticks at 100ms intervals - documented and justified
Requirements:
  1. First wait for triggering condition
  2. Based on known timing (not guessing)
  3. Comment explaining WHY
typescript
// 工具每100ms执行一次——需要等待2个周期以验证部分输出
await waitForEvent(manager, 'TOOL_STARTED'); // 第一步:等待触发条件
await new Promise(r => setTimeout(r, 200));   // 第二步:等待时序行为
// 200ms = 100ms间隔的2个周期——已记录并说明合理性
要求:
  1. 先等待触发条件
  2. 基于已知时序(而非猜测)
  3. 添加注释说明原因

Real-World Impact

实际业务影响

From debugging session (2025-10-03):
  • Fixed 15 flaky tests across 3 files
  • Pass rate: 60% → 100%
  • Execution time: 40% faster
  • No more race conditions
来自调试会话(2025-10-03):
  • 修复了3个文件中的15个不稳定测试
  • 通过率:60% → 100%
  • 执行时间:缩短40%
  • 不再出现竞态条件