Awesome Reviewers expert instructions

domains / / microsoft/VSCode

Graceful error handling

When handling failures in UI/async flows, design for graceful degradation and predictable recovery: - Treat async outcomes as explicit states (uninitialized/loading/loaded/failed). Never let “empty success” be conflated with “not fetched,” and avoid recursive re-fetch loops.

raw .md Error Handling TypeScript

When handling failures in UI/async flows, design for graceful degradation and predictable recovery:

  • Treat async outcomes as explicit states (uninitialized/loading/loaded/failed). Never let “empty success” be conflated with “not fetched,” and avoid recursive re-fetch loops.
  • For non-critical optional capabilities, use best-effort behavior: catch RPC/config lookup failures, log a warning, and keep the primary workflow running.
  • Preserve user-facing invariants during errors (e.g., focus, visible/selected surface), and make recovery deterministic (retry/fallback paths should restore the right pending state).
  • Ensure cleanup and diagnostics always run: use try/finally to dispose resources and capture logs, even if closing/teardown throws.
  • Validate inputs defensively before coercion/processing (e.g., handle NaN and out-of-bounds early) so errors are consistent and don’t cascade.

Example (pattern):

type SnapshotState = 'uninitialized' | 'loading' | 'loaded' | 'failed';

let state: SnapshotState = 'uninitialized';

async function ensureSnapshot() {
  if (state !== 'uninitialized') return; // don't loop on empty success
  state = 'loading';
  try {
    const result = await queryMarketplaceSnapshot();
    // empty array is still a terminal loaded-success
    render(result);
    state = 'loaded';
  } catch (e) {
    state = 'failed';
    showRetryUI();
    // focus Retry/error so recovery is usable
    focusRetry();
  }
}

async function optionalCapability() {
  try {
    return await rpc.getBuiltinDescriptors({});
  } catch (e) {
    logService.warn('Optional capability unavailable; continuing with defaults', e);
    return undefined; // fail open
  }
}

async function runWithCleanup() {
  const accessor = services.createTestingAccessor();
  try {
    return await work(accessor);
  } finally {
    accessor.dispose();
    captureLogsIfNeeded();
  }
}

Adopting this approach prevents the most painful failure modes seen in the discussions: infinite re-render/request loops, core workflows breaking due to auxiliary RPC failures, broken UX recovery (like lost focus), and missing cleanup/log capture during failures.