agentsclimarketplace

Error handling

Skill ComeOnOliver/skillshub/skills/aiskillstore/marketplace/doyajin174/error-handling

🧠 The right skill, one API call. AI agent skills registry with token-efficient skill resolution. 5,000+ skills from 500+ top repos.

Install
npx -y skills add ComeOnOliver/skillshub --skill error-handling

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Enforce proper error handling patterns. Use when writing async code, API calls, or user-facing features. Covers try-catch, error boundaries, graceful degradation, and user feedback.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

9.9 KB, as published. Nobody here has run it

Error Handling Patterns

μ μ ˆν•œ μ—λŸ¬ 처리 νŒ¨ν„΄μ„ κ°•μ œν•˜λŠ” μŠ€ν‚¬μž…λ‹ˆλ‹€.

Core Principle

"μ—λŸ¬λŠ” μˆ¨κΈ°μ§€ μ•Šκ³ , 적절히 μ²˜λ¦¬ν•˜κ³ , μ‚¬μš©μžμ—κ²Œ μ•Œλ¦°λ‹€." "Fail gracefully, recover when possible."

Rules

κ·œμΉ™μƒνƒœμ„€λͺ…
빈 catch 블둝 κΈˆμ§€πŸ”΄ ν•„μˆ˜μ΅œμ†Œ λ‘œκΉ… ν•„μˆ˜
μ‚¬μš©μž μΉœν™”μ  λ©”μ‹œμ§€πŸ”΄ ν•„μˆ˜κΈ°μˆ μ  μ—λŸ¬ λ©”μ‹œμ§€ λ…ΈμΆœ κΈˆμ§€
Error Boundary μ‚¬μš©πŸ”΄ ν•„μˆ˜ (React)μ»΄ν¬λ„ŒνŠΈ μ—λŸ¬ 격리
Graceful Degradation🟑 ꢌμž₯λΆ€λΆ„ μ‹€νŒ¨ μ‹œ λŒ€μ•ˆ 제곡

κΈ°λ³Έ νŒ¨ν„΄

Try-Catch μ˜¬λ°”λ₯Έ μ‚¬μš©

// ❌ BAD: 빈 catch 블둝
try {
  await fetchData();
} catch (e) {
  // 아무것도 μ•ˆ 함 - μ—λŸ¬ λ¬΄μ‹œ
}

// ❌ BAD: λͺ¨λ“  μ—λŸ¬ 동일 처리
try {
  await fetchData();
} catch (e) {
  console.log('μ—λŸ¬ λ°œμƒ');  // 정보 λΆ€μ‘±
}

// βœ… GOOD: μ μ ˆν•œ μ—λŸ¬ 처리
try {
  await fetchData();
} catch (error) {
  // 1. μ—λŸ¬ λ‘œκΉ… (개발자용)
  console.error('fetchData failed:', error);

  // 2. μ—λŸ¬ 좔적 μ„œλΉ„μŠ€ 전솑
  errorTracker.capture(error);

  // 3. μ‚¬μš©μžμ—κ²Œ μ•Œλ¦Ό
  showToast('데이터λ₯Ό λΆˆλŸ¬μ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.');

  // 4. ν•„μš”μ‹œ μž¬μ‹œλ„ λ˜λŠ” λŒ€μ•ˆ 제곡
  return fallbackData;
}

μ—λŸ¬ νƒ€μž… ꡬ뢄

// βœ… GOOD: μ—λŸ¬ νƒ€μž…λ³„ 처리
async function fetchUser(id: string) {
  try {
    const response = await api.get(`/users/${id}`);
    return response.data;
  } catch (error) {
    if (error instanceof NetworkError) {
      // λ„€νŠΈμ›Œν¬ μ—λŸ¬: μž¬μ‹œλ„ μ œμ•ˆ
      showToast('λ„€νŠΈμ›Œν¬ 연결을 ν™•μΈν•΄μ£Όμ„Έμš”.');
      return null;
    }

    if (error instanceof NotFoundError) {
      // 404: μ‚¬μš©μž μ—†μŒ
      showToast('μ‚¬μš©μžλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.');
      return null;
    }

    if (error instanceof AuthError) {
      // 인증 μ—λŸ¬: 둜그인 νŽ˜μ΄μ§€λ‘œ
      router.push('/login');
      return null;
    }

    // μ˜ˆμƒμΉ˜ λͺ»ν•œ μ—λŸ¬
    console.error('Unexpected error:', error);
    errorTracker.capture(error);
    showToast('였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.');
    return null;
  }
}

μ»€μŠ€ν…€ μ—λŸ¬ 클래슀

// errors.ts
export class AppError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode?: number,
    public isOperational: boolean = true
  ) {
    super(message);
    this.name = 'AppError';
  }
}

export class ValidationError extends AppError {
  constructor(message: string, public field?: string) {
    super(message, 'VALIDATION_ERROR', 400);
    this.name = 'ValidationError';
  }
}

export class NetworkError extends AppError {
  constructor(message: string = 'λ„€νŠΈμ›Œν¬ 연결을 ν™•μΈν•΄μ£Όμ„Έμš”') {
    super(message, 'NETWORK_ERROR', 0);
    this.name = 'NetworkError';
  }
}

export class NotFoundError extends AppError {
  constructor(resource: string) {
    super(`${resource}을(λ₯Ό) 찾을 수 μ—†μŠ΅λ‹ˆλ‹€`, 'NOT_FOUND', 404);
    this.name = 'NotFoundError';
  }
}

React Error Boundary

κΈ°λ³Έ Error Boundary

// ErrorBoundary.tsx
import { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
  onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}

interface State {
  hasError: boolean;
  error?: Error;
}

export class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false };

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    this.props.onError?.(error, errorInfo);

    // μ—λŸ¬ 좔적 μ„œλΉ„μŠ€λ‘œ 전솑
    errorTracker.captureException(error, { extra: errorInfo });
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <DefaultErrorFallback error={this.state.error} />;
    }
    return this.props.children;
  }
}

// 기본 폴백 UI
function DefaultErrorFallback({ error }: { error?: Error }) {
  return (
    <div className="error-fallback">
      <h2>λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€</h2>
      <p>νŽ˜μ΄μ§€λ₯Ό μƒˆλ‘œκ³ μΉ¨ν•˜κ±°λ‚˜ μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.</p>
      <button onClick={() => window.location.reload()}>
        μƒˆλ‘œκ³ μΉ¨
      </button>
    </div>
  );
}

Error Boundary μ‚¬μš©

// μ•± 전체 감싸기
function App() {
  return (
    <ErrorBoundary fallback={<FullPageError />}>
      <Router>
        <Routes />
      </Router>
    </ErrorBoundary>
  );
}

// νŠΉμ • μ„Ήμ…˜λ§Œ 감싸기
function Dashboard() {
  return (
    <div>
      <Header />
      <ErrorBoundary fallback={<ChartError />}>
        <Chart data={data} />
      </ErrorBoundary>
      <ErrorBoundary fallback={<TableError />}>
        <DataTable data={data} />
      </ErrorBoundary>
    </div>
  );
}

Async μ—λŸ¬ 처리

Promise μ—λŸ¬

// ❌ BAD: unhandled rejection
fetchData().then(data => setData(data));

// βœ… GOOD: catch 처리
fetchData()
  .then(data => setData(data))
  .catch(error => {
    console.error('Failed to fetch:', error);
    setError(error);
  });

// βœ… BETTER: async/await
async function loadData() {
  try {
    const data = await fetchData();
    setData(data);
  } catch (error) {
    console.error('Failed to fetch:', error);
    setError(error);
  }
}

μ—¬λŸ¬ Promise 처리

// ❌ BAD: ν•˜λ‚˜λΌλ„ μ‹€νŒ¨ν•˜λ©΄ 전체 μ‹€νŒ¨
const [users, posts] = await Promise.all([
  fetchUsers(),
  fetchPosts(),
]);

// βœ… GOOD: κ°œλ³„ κ²°κ³Ό 처리
const results = await Promise.allSettled([
  fetchUsers(),
  fetchPosts(),
]);

const users = results[0].status === 'fulfilled' ? results[0].value : [];
const posts = results[1].status === 'fulfilled' ? results[1].value : [];

// μ‹€νŒ¨ν•œ κ²ƒλ§Œ λ‘œκΉ…
results
  .filter((r): r is PromiseRejectedResult => r.status === 'rejected')
  .forEach(r => console.error('Failed:', r.reason));

Graceful Degradation

κΈ°λŠ₯ μ €ν•˜ νŒ¨ν„΄

// βœ… GOOD: μ‹€νŒ¨ μ‹œ λŒ€μ•ˆ 제곡
async function getRecommendations(userId: string) {
  try {
    // 1μ°¨: κ°œμΈν™”λœ μΆ”μ²œ
    return await fetchPersonalizedRecommendations(userId);
  } catch (error) {
    console.warn('Personalized recommendations failed:', error);

    try {
      // 2μ°¨: 인기 μ½˜ν…μΈ 
      return await fetchPopularContent();
    } catch (error) {
      console.warn('Popular content failed:', error);

      // 3μ°¨: μΊμ‹œλœ κΈ°λ³Έ μΆ”μ²œ
      return getCachedDefaultRecommendations();
    }
  }
}

UI λŒ€μ•ˆ 제곡

function UserAvatar({ userId }: { userId: string }) {
  const [imageError, setImageError] = useState(false);
  const user = useUser(userId);

  if (imageError || !user?.avatarUrl) {
    // 이미지 λ‘œλ“œ μ‹€νŒ¨ μ‹œ λŒ€μ•ˆ
    return (
      <div className="avatar-placeholder">
        {user?.name?.charAt(0) || '?'}
      </div>
    );
  }

  return (
    <img
      src={user.avatarUrl}
      alt={user.name}
      onError={() => setImageError(true)}
    />
  );
}

μ‚¬μš©μž μΉœν™”μ  λ©”μ‹œμ§€

λ©”μ‹œμ§€ λ§€ν•‘

const errorMessages: Record<string, string> = {
  NETWORK_ERROR: 'λ„€νŠΈμ›Œν¬ 연결을 ν™•μΈν•΄μ£Όμ„Έμš”.',
  UNAUTHORIZED: '둜그인이 ν•„μš”ν•©λ‹ˆλ‹€.',
  FORBIDDEN: 'μ ‘κ·Ό κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.',
  NOT_FOUND: 'μš”μ²­ν•œ 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.',
  VALIDATION_ERROR: 'μž…λ ₯ 정보λ₯Ό ν™•μΈν•΄μ£Όμ„Έμš”.',
  RATE_LIMIT: 'μš”μ²­μ΄ λ„ˆλ¬΄ λ§ŽμŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.',
  SERVER_ERROR: 'μ„œλ²„ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.',
  DEFAULT: '였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.',
};

function getUserFriendlyMessage(error: unknown): string {
  if (error instanceof AppError) {
    return errorMessages[error.code] || errorMessages.DEFAULT;
  }
  return errorMessages.DEFAULT;
}

πŸ”΄ κΈˆμ§€: 기술적 λ©”μ‹œμ§€ λ…ΈμΆœ

// ❌ BAD: μ‚¬μš©μžμ—κ²Œ 기술적 λ©”μ‹œμ§€ ν‘œμ‹œ
showToast(error.message);  // "TypeError: Cannot read property 'id' of undefined"
showToast(error.stack);    // μŠ€νƒ 트레이슀 λ…ΈμΆœ

// βœ… GOOD: μΉœν™”μ  λ©”μ‹œμ§€
showToast(getUserFriendlyMessage(error));

λ‘œκΉ… μ „λž΅

// logger.ts
export const logger = {
  error: (message: string, error: unknown, context?: object) => {
    // 개발 ν™˜κ²½: μ½˜μ†” 좜λ ₯
    if (process.env.NODE_ENV === 'development') {
      console.error(message, error, context);
    }

    // ν”„λ‘œλ•μ…˜: μ—λŸ¬ 좔적 μ„œλΉ„μŠ€
    errorTracker.captureException(error, {
      tags: { message },
      extra: context,
    });
  },

  warn: (message: string, context?: object) => {
    console.warn(message, context);
  },
};

Checklist

μ½”λ“œ μž‘μ„± μ‹œ

  • try-catch에 μ μ ˆν•œ μ—λŸ¬ 처리 둜직
  • 빈 catch 블둝 μ—†μŒ
  • μ—λŸ¬ νƒ€μž…λ³„ λΆ„κΈ° 처리
  • μ‚¬μš©μž μΉœν™”μ  λ©”μ‹œμ§€ ν‘œμ‹œ
  • μ—λŸ¬ λ‘œκΉ…/좔적

React μ»΄ν¬λ„ŒνŠΈ

  • Error Boundary 적용
  • λ‘œλ”©/μ—λŸ¬ μƒνƒœ UI
  • μž¬μ‹œλ„ κΈ°λŠ₯ 제곡
  • 폴백 UI κ΅¬ν˜„

API 호좜

  • λ„€νŠΈμ›Œν¬ μ—λŸ¬ 처리
  • νƒ€μž„μ•„μ›ƒ 처리
  • μž¬μ‹œλ„ 둜직 (ν•„μš”μ‹œ)
  • μΊμ‹œ 폴백 (ν•„μš”μ‹œ)

References

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.