๐Ÿšจ 11. ์—๋Ÿฌ ์ฒ˜๋ฆฌ โ€” ์˜ˆ์™ธ๋ฅผ ์šฐ์•„ํ•˜๊ฒŒ ๋‹ค๋ฃจ๋Š” ์‹œ๋‹ˆ์–ด์˜ ๋ฐฉ์–ด ์ „๋žต

2026๋…„ 3์›” 8์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

try/catch์˜ ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ, ์ปค์Šคํ…€ ์—๋Ÿฌ ํด๋ž˜์Šค, ๋น„๋™๊ธฐ ์—๋Ÿฌ ์ „ํŒŒ, ์ „์—ญ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ, ์‹ค๋ฌด ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ „๋žต์„ ์™„์ „ ์ •๋ณตํ•ฉ๋‹ˆ๋‹ค.

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ์ปค์Šคํ…€ ์—๋Ÿฌ ํด๋ž˜์Šค๋กœ ์—๋Ÿฌ๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๊ณ  ํƒ€์ž…๋ณ„๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  • ๋น„๋™๊ธฐ ํ•จ์ˆ˜์—์„œ ์—๋Ÿฌ๊ฐ€ ์ „ํŒŒ๋˜๋Š” ๋ฐฉ์‹์„ ์ดํ•ดํ•˜๊ณ  ๋น ์ง์—†์ด ์ฒ˜๋ฆฌํ•œ๋‹ค.
  • ์ „์—ญ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ์™€ ๋กœ๊น… ์ „๋žต์œผ๋กœ ์‹ค๋ฌด ์—๋Ÿฌ๋ฅผ ์ถ”์ ํ•œ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 18๋ถ„(์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 11๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
[Error ํƒ€์ž…] โ†’ [try/catch ํŒจํ„ด] โ†’ [์ปค์Šคํ…€ ์—๋Ÿฌ] โ†’ [๋น„๋™๊ธฐ ์—๋Ÿฌ] โ†’ [์ „์—ญ ํ•ธ๋“ค๋Ÿฌ]

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • ์—๋Ÿฌ ํƒ€์ž…์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ์กฐํ™”๋œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ๊ตฌํ˜„ํ•œ๋‹ค.
  • API ์—๋Ÿฌ๋ฅผ ์ปค์Šคํ…€ ์—๋Ÿฌ ํด๋ž˜์Šค๋กœ ๋ž˜ํ•‘ํ•ด ์˜๋ฏธ ์žˆ๋Š” ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.
  • unhandledrejection ์ด๋ฒคํŠธ๋กœ Promise ์—๋Ÿฌ ๋ˆ„๋ฝ์„ ๋ฐฉ์ง€ํ•œ๋‹ค.

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

๐Ÿฃ ์˜์ฒ : "์˜ํ˜ธ ๋‹˜, ์–ด์ œ ๋ฐฐํฌํ–ˆ๋Š”๋ฐ ์ผ๋ถ€ ์œ ์ €๊ฐ€ '๊ฒŒ์‹œ๊ธ€ ์ €์žฅ์ด ์•ˆ ๋œ๋‹ค'๊ณ  ํ•˜๋Š”๋ฐ, ๋กœ๊ทธ์—๋Š” ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ Error: Failed to fetch๋ฐ–์— ์—†์–ด์š”. ๋ญ๊ฐ€ ๋ฌธ์ œ์ธ์ง€ ์ „ํ˜€ ๋ชจ๋ฅด๊ฒ ์–ด์š”. ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ์ธ์ง€, ์„œ๋ฒ„ ์—๋Ÿฌ์ธ์ง€, ์ธ์ฆ ์—๋Ÿฌ์ธ์ง€... ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋„ˆ๋ฌด ๋นˆ์•ฝํ•ด์š”."

๐Ÿฆ ์˜ํ˜ธ: "์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ์•ˆ ๋œ ๊ฒŒ ์•„๋‹ˆ๋ผ ์—๋Ÿฌ๊ฐ€ ์ œ๋Œ€๋กœ ๊ตฌ์กฐํ™”๋˜์ง€ ์•Š์€ ๊ฑฐ์•ผ. ์ปค์Šคํ…€ ์—๋Ÿฌ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ ์—๋Ÿฌ ํƒ€์ž…, HTTP ์ƒํƒœ ์ฝ”๋“œ, ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€, ๊ฐœ๋ฐœ์ž ๋ฉ”์‹œ์ง€๋ฅผ ๋ถ„๋ฆฌํ•ด์•ผ ํ•ด. Failed to fetch ํ•˜๋‚˜๋กœ๋Š” ๋””๋ฒ„๊น…์ด ๋ถˆ๊ฐ€๋Šฅํ•ด. ์—๋Ÿฌ๋„ ์„ค๊ณ„๊ฐ€ ํ•„์š”ํ•ด."


๐Ÿค” 1. ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€?

์˜์ˆ™ ๋‹˜์ด "๊ฒŒ์‹œ๊ธ€ ์ €์žฅ ๋ˆ„๋ฅด๋ฉด ๊ทธ๋ƒฅ ํฐ ํ™”๋ฉด์ด ๋‚˜์™€์š”"๋ผ๊ณ  ์ œ๋ณดํ•œ ๋‚ , ์˜์ˆ˜ ๋‹˜์ด ์„œ๋ฒ„ ๋กœ๊ทธ๋ฅผ ์—ด์—ˆ๋”๋‹ˆ Error: undefined ํ•œ ์ค„์ด ์ „๋ถ€์˜€๋‹ค. ์–ด๋А ์„œ๋ฒ„์ธ์ง€, ์–ด๋А API์ธ์ง€, ์–ด๋–ค ์œ ์ €์ธ์ง€ โ€” ์•„๋ฌด ๋‹จ์„œ๋„ ์—†์—ˆ๋‹ค. ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ "try/catch ๊ฐ์‹ธ๋ฉด ๋"์œผ๋กœ ์•Œ๋ฉด ๋ฐ”๋กœ ์ด ์ƒํ™ฉ์— ๋ฌด๋ ฅํ•ด์ง„๋‹ค:

  • ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋Š”๋ฐ "Error: undefined"๋งŒ ๋กœ๊ทธ์— ๋‚จ๋Š” ๊ฒฝ์šฐ
  • Promise chain์—์„œ ์—๋Ÿฌ๊ฐ€ ์กฐ์šฉํžˆ ์‚ผ์ผœ์ง€๋Š” ๊ฒฝ์šฐ
  • ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ๊ธฐ์ˆ ์  ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœ๋˜๋Š” ๊ฒฝ์šฐ
  • ์–ด๋А API์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋Š”์ง€ ์ถ”์ ์ด ์•ˆ ๋˜๋Š” ๊ฒฝ์šฐ

์ข‹์€ ์—๋Ÿฌ ์ฒ˜๋ฆฌ = ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ + ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜ ๋™์‹œ์— ์ง€ํ‚จ๋‹ค.


๐Ÿ—๏ธ 2. Error ๊ฐ์ฒด์™€ ์ข…๋ฅ˜

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” Error ๊ฐ์ฒด๋Š” name, message, stack ์„ธ ๊ฐ€์ง€ ํ•ต์‹ฌ ์†์„ฑ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์€ ์—๋Ÿฌ์˜ ๋ฐœ์ƒ ์›์ธ(ํƒ€์ž…)์„ ๋จผ์ € ์‹๋ณ„ํ•ด์•ผ ์ƒํ™ฉ์— ๋งž๋Š” ๋ฐฉ์–ด ๋กœ์ง์„ ์งค ์ˆ˜ ์žˆ๋‹ค๊ณ  ์กฐ์–ธํ•ฉ๋‹ˆ๋‹ค.

// ๊ธฐ๋ณธ Error ์†์„ฑ
try {
  null.property; // TypeError ๋ฐœ์ƒ
} catch (err) {
  console.log(err.name);    // "TypeError"
  console.log(err.message); // "Cannot read properties of null..."
  console.log(err.stack);   // ์Šคํƒ ํŠธ๋ ˆ์ด์Šค (๋””๋ฒ„๊น…์˜ ํ•ต์‹ฌ)
}
 
// JS ๋‚ด์žฅ ์—๋Ÿฌ ํƒ€์ž…
// TypeError    โ€” ํƒ€์ž…์ด ๋งž์ง€ ์•Š์„ ๋•Œ (null.foo, undefined())
// ReferenceError โ€” ์„ ์–ธ๋˜์ง€ ์•Š์€ ๋ณ€์ˆ˜ ์ฐธ์กฐ (๋ฏธ์„ ์–ธ ๋ณ€์ˆ˜)
// SyntaxError  โ€” ๋ฌธ๋ฒ• ์˜ค๋ฅ˜ (ํŒŒ์‹ฑ ๋‹จ๊ณ„์—์„œ ๋ฐœ์ƒ, catch๋กœ ๋ชป ์žก์Œ)
// RangeError   โ€” ํ—ˆ์šฉ ๋ฒ”์œ„ ์ดˆ๊ณผ (๋ฐฐ์—ด ๊ธธ์ด ์Œ์ˆ˜ ๋“ฑ)
// URIError     โ€” encodeURI ๋“ฑ์—์„œ ์ž˜๋ชป๋œ URI

๐Ÿ›ก๏ธ 3. try/catch/finally ํŒจํ„ด

์˜์ฒ ์ด๊ฐ€ ์ฒ˜์Œ ์™ธ๋ถ€ API๋ฅผ ์—ฐ๋™ํ•  ๋•Œ, ์„ฑ๊ณตํ•˜๋Š” ๊ฒฝ์šฐ๋งŒ ์ƒ๊ฐํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค๊ฐ€ ์„œ๋ฒ„ ์žฅ์•  ์‹œ ํ™”๋ฉด์ด ๋ฉŽ์–ด๋ฒ„๋ฆฐ ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์€ "์‹คํŒจ๋ฅผ ์šฐ์•„ํ•˜๊ฒŒ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์ด ์‹œ๋‹ˆ์–ด์˜ ๊ธฐ๋ณธ๊ธฐ"๋ผ๋ฉฐ try/catch/finally์˜ ์ •ํ™•ํ•œ ํ๋ฆ„์„ ์ œ์•ˆํ–ˆ์Šต๋‹ˆ๋‹ค.

// ๊ธฐ๋ณธ ํŒจํ„ด
function parseUserData(jsonString) {
  try {
    return JSON.parse(jsonString);
  } catch (err) {
    if (err instanceof SyntaxError) {
      // ํƒ€์ž…๋ณ„ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ
      console.error("JSON ํŒŒ์‹ฑ ์‹คํŒจ:", err.message);
      return null;
    }
    throw err; // ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ๋Š” ๋‹ค์‹œ ๋˜์ง€๊ธฐ
  }
}
 
// finally โ€” ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ์— ์‚ฌ์šฉ
async function withDbConnection(operation) {
  const connection = await getDbConnection();
  try {
    return await operation(connection);
  } catch (err) {
    console.error("DB ์ž‘์—… ์‹คํŒจ:", err);
    throw err;
  } finally {
    // ์„ฑ๊ณต/์‹คํŒจ ๋ฌด๊ด€ํ•˜๊ฒŒ ํ•ญ์ƒ ์—ฐ๊ฒฐ ํ•ด์ œ
    await connection.close();
  }
}
 
// โŒ ํ”ํ•œ ์‹ค์ˆ˜ โ€” ์—๋Ÿฌ ์‚ผํ‚ค๊ธฐ (Silent Failure)
try {
  await riskyOperation();
} catch (err) {
  // ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•จ โ€” ์—๋Ÿฌ๊ฐ€ ์‚ฌ๋ผ์ง!
  // ์ด๊ฒƒ๋ณด๋‹ค๋Š” ์ตœ์†Œํ•œ ๋กœ๊ทธ๋ผ๋„ ๋‚จ๊ฒจ์•ผ ํ•œ๋‹ค
}
 
// โœ… ์—๋Ÿฌ๋ฅผ ์‚ผ์ผœ์•ผ ํ•œ๋‹ค๋ฉด ์˜๋„๋ฅผ ๋ช…์‹œ
try {
  await optionalOperation(); // ์‹คํŒจํ•ด๋„ ๊ดœ์ฐฎ์€ ์ž‘์—…
} catch (err) {
  // ์˜๋„์ ์œผ๋กœ ๋ฌด์‹œ: ์ด ์ž‘์—…์€ ์‹คํŒจํ•ด๋„ ๋ฉ”์ธ ํ”Œ๋กœ์šฐ์— ์˜ํ–ฅ ์—†์Œ
  console.warn("์„ ํƒ์  ์ž‘์—… ์‹คํŒจ (๋ฌด์‹œ๋จ):", err.message);
}

๐ŸŽจ 4. ์ปค์Šคํ…€ ์—๋Ÿฌ ํด๋ž˜์Šค

์˜คํ”„๋‹์—์„œ ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด "์—๋Ÿฌ๋„ ์„ค๊ณ„๊ฐ€ ํ•„์š”ํ•ด"๋ผ๊ณ  ํ–ˆ๋˜ ๊ทธ ์„ค๊ณ„ ๊ฒฐ๊ณผ๋ฌผ์ด๋‹ค. Failed to fetch ํ•˜๋‚˜๋กœ๋Š” ์ธ์ฆ ์—๋Ÿฌ์ธ์ง€, ๊ถŒํ•œ ์—๋Ÿฌ์ธ์ง€, ๋ฆฌ์†Œ์Šค ์—๋Ÿฌ์ธ์ง€ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์—†๋‹ค. ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด ํŒ€์— ๋„์ž…ํ•œ ์—๋Ÿฌ ๊ณ„์ธต ๊ตฌ์กฐ ์ „์ฒด๋ฅผ ๋ณด์ž.

// ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ โ€” API ์—๋Ÿฌ ๊ณ„์ธต ๊ตฌ์กฐ
 
// ๊ธฐ๋ฐ˜ ์—๋Ÿฌ ํด๋ž˜์Šค
class AppError extends Error {
  constructor(message, options = {}) {
    super(message);
    this.name = this.constructor.name; // "AppError", "ApiError" ๋“ฑ ์ž๋™ ์„ค์ •
    this.statusCode = options.statusCode ?? 500;
    this.userMessage = options.userMessage ?? "์„œ๋ฒ„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.";
    this.isOperational = options.isOperational ?? true; // ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์—๋Ÿฌ์ธ๊ฐ€?
    this.context = options.context ?? {}; // ๋””๋ฒ„๊น…์šฉ ์ปจํ…์ŠคํŠธ
 
    // V8์—์„œ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ์ •ํ™•ํ•˜๊ฒŒ ์œ ์ง€
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor);
    }
  }
}
 
// API ์—๋Ÿฌ
class ApiError extends AppError {
  constructor(message, statusCode, options = {}) {
    super(message, { statusCode, ...options });
  }
}
 
// ์ธ์ฆ/์ธ๊ฐ€ ์—๋Ÿฌ
class AuthError extends ApiError {
  constructor(message = "์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", options = {}) {
    super(message, 401, {
      userMessage: "๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.",
      ...options,
    });
  }
}
 
class ForbiddenError extends ApiError {
  constructor(message = "๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.", options = {}) {
    super(message, 403, {
      userMessage: "์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.",
      ...options,
    });
  }
}
 
// ๋ฆฌ์†Œ์Šค ์—†์Œ
class NotFoundError extends ApiError {
  constructor(resource = "๋ฆฌ์†Œ์Šค", options = {}) {
    super(`${resource}๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.`, 404, {
      userMessage: `์š”์ฒญํ•œ ${resource}๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.`,
      ...options,
    });
  }
}
 
// ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์—๋Ÿฌ
class ValidationError extends AppError {
  constructor(message, fields = {}) {
    super(message, {
      statusCode: 400,
      userMessage: "์ž…๋ ฅ๊ฐ’์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.",
      context: { fields },
    });
    this.fields = fields; // ํ•„๋“œ๋ณ„ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€
  }
}
// ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ
 
async function updatePost(postId, data, currentUser) {
  // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
  if (!data.title?.trim()) {
    throw new ValidationError("์ œ๋ชฉ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.", {
      title: "์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.",
    });
  }
 
  const post = await findPost(postId);
  if (!post) {
    throw new NotFoundError("๊ฒŒ์‹œ๊ธ€");
  }
 
  if (post.authorId !== currentUser.id && !currentUser.isAdmin) {
    throw new ForbiddenError("๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • ๊ถŒํ•œ ์—†์Œ", {
      context: { postId, userId: currentUser.id },
    });
  }
 
  return await savePost(postId, data);
}
 
// ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ธก
async function handleUpdatePost(req, res) {
  try {
    const result = await updatePost(req.params.id, req.body, req.user);
    res.json({ success: true, data: result });
  } catch (err) {
    if (err instanceof ValidationError) {
      return res.status(400).json({
        error: err.userMessage,
        fields: err.fields,
      });
    }
    if (err instanceof ForbiddenError) {
      return res.status(403).json({ error: err.userMessage });
    }
    if (err instanceof NotFoundError) {
      return res.status(404).json({ error: err.userMessage });
    }
 
    // ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ โ€” 500 ๋ฐ˜ํ™˜ + ๋กœ๊น…
    console.error("์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ:", err);
    res.status(500).json({ error: "์„œ๋ฒ„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค." });
  }
}

โšก 5. ๋น„๋™๊ธฐ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

๋™๊ธฐ์ ์ธ ๋กœ์ง์—์„œ๋Š” try/catch๊ฐ€ ์ง๊ด€์ ์ด์ง€๋งŒ, ๋น„๋™๊ธฐ ์„ธ๊ณ„์—์„œ๋Š” ์—๋Ÿฌ๊ฐ€ ๋‹ค๋ฅธ ์‹œ์ ์œผ๋กœ ๋„˜์–ด๊ฐ€ ๋ฒ„๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์ข…์ข… ๋ฐฉ์–ด์„ ์„ ํ†ต๊ณผํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์˜์ฒ ์ด๊ฐ€ await๋‚˜ .catch๋ฅผ ๋ˆ„๋ฝํ•ด ์—๋Ÿฌ๋ฅผ ํ—ˆ๊ณต์— ๋‚ ๋ ค๋ฒ„๋ ธ๋˜ ๊ฒฝํ—˜์„ ๋ฐ”ํƒ•์œผ๋กœ ์•ˆ์ „๋ง์„ ๊ตฌ์ถ•ํ•ด ๋ด…๋‹ˆ๋‹ค.

// Promise ์—๋Ÿฌ ์ฒ˜๋ฆฌ โ€” .catch ๋น ๋œจ๋ฆฌ๋ฉด ์•ˆ ๋จ
fetchPost(1)
  .then((post) => renderPost(post))
  .catch((err) => {
    // ์–ด๋А ๋‹จ๊ณ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋„ ์—ฌ๊ธฐ์„œ ์ฒ˜๋ฆฌ
    showError(err.userMessage ?? err.message);
  });
 
// async/await ์—๋Ÿฌ ์ฒ˜๋ฆฌ
async function loadPost(postId) {
  try {
    const post = await fetchPost(postId);
    return post;
  } catch (err) {
    if (err instanceof NotFoundError) {
      return null; // ์—†๋Š” ๊ฒŒ์‹œ๊ธ€์€ null ๋ฐ˜ํ™˜
    }
    throw err; // ๋‹ค๋ฅธ ์—๋Ÿฌ๋Š” ์œ„๋กœ ์ „ํŒŒ
  }
}
 
// โŒ ์—๋Ÿฌ ์ „ํŒŒ ๋ˆ„๋ฝ ํŒจํ„ด (ํ”ํ•œ ์‹ค์ˆ˜)
async function badExample() {
  const posts = await fetchPosts(); // ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ...
  // try/catch ์—†์Œ โ†’ ์—๋Ÿฌ๊ฐ€ ์ƒ์œ„๋กœ ์ „ํŒŒ๋จ (์—ฌ๊ธฐ์„  OK)
  return posts;
}
 
// ๋ฌธ์ œ: ํ˜ธ์ถœ ์ธก์—์„œ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด
badExample(); // Promise rejection์ด ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์Œ โ†’ UnhandledPromiseRejection
 
// โœ… ํ•ญ์ƒ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ณด์žฅ
try {
  await badExample();
} catch (err) {
  handleError(err);
}

๐ŸŒ 6. ์ „์—ญ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ณณ๊ณณ์— try/catch๋ฅผ ๊ผผ๊ผผํ•˜๊ฒŒ ๋ฐฐ์น˜ํ•ด๋„, ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๊ณณ์—์„œ ์—๋Ÿฌ๋Š” ๋ฐ˜๋“œ์‹œ ํŠ€์–ด๋‚˜์˜ค๊ธฐ ๋งˆ๋ จ์ž…๋‹ˆ๋‹ค. ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์€ ์ตœํ›„์˜ ๋ณด๋ฃจ๋กœ ์ „์—ญ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์„ธํŒ…ํ•˜์—ฌ, ๋ˆ„์ˆ˜๋œ ์—๋Ÿฌ๋“ค์ด ์กฐ์šฉํžˆ ๋ฌปํžˆ์ง€ ์•Š๊ณ  ๊ด€์ œ ์‹œ์Šคํ…œ์œผ๋กœ ๋ชจ์ด๋„๋ก ์กฐ์น˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

// ๋ธŒ๋ผ์šฐ์ € โ€” ์ „์—ญ ์—๋Ÿฌ ์บ์น˜
 
// ๋™๊ธฐ ์—๋Ÿฌ (try/catch๋กœ ์žก์ง€ ๋ชปํ•œ ๊ฒƒ)
window.addEventListener("error", (event) => {
  console.error("์ „์—ญ ์—๋Ÿฌ:", event.error);
  sendToErrorTracking({
    message: event.error?.message,
    stack: event.error?.stack,
    filename: event.filename,
    lineno: event.lineno,
  });
  // event.preventDefault()๋กœ ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ ์—๋Ÿฌ ๋กœ๊ทธ ์–ต์ œ ๊ฐ€๋Šฅ
});
 
// ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ Promise rejection
window.addEventListener("unhandledrejection", (event) => {
  console.error("์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ Promise ๊ฑฐ๋ถ€:", event.reason);
  sendToErrorTracking({
    message: event.reason?.message ?? String(event.reason),
    stack: event.reason?.stack,
    type: "UnhandledPromiseRejection",
  });
  event.preventDefault(); // ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†” ๊ฒฝ๊ณ  ์–ต์ œ
});
 
// Node.js ์ „์—ญ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ
process.on("uncaughtException", (err) => {
  console.error("์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ˆ์™ธ:", err);
  // ๋กœ๊น… ํ›„ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ (uncaughtException ํ›„ ์ƒํƒœ ๋ถˆ์•ˆ์ •)
  process.exit(1);
});
 
process.on("unhandledRejection", (reason, promise) => {
  console.error("์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ Promise ๊ฑฐ๋ถ€:", reason);
  // ์‹ฌ๊ฐํ•œ ์—๋Ÿฌ๋ฉด ์ข…๋ฃŒ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๊ณ„์† ์‹คํ–‰
});

๐Ÿ’ผ 7. ์‹ค๋ฌด ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ „๋žต

์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด Sentry ์—ฐ๋™ ์ž‘์—…๊ณผ ํ•จ๊ป˜ ํŒ€ ์ „์ฒด์— ์ ์šฉํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์•„ํ‚คํ…์ฒ˜๋‹ค. ์˜์ฒ ์ด๊ฐ€ "์ด๊ฑฐ ๋งค๋ฒˆ ์„ค์ •ํ•ด์•ผ ํ•ด์š”?"๋ผ๊ณ  ๋ฌผ์—ˆ์„ ๋•Œ, ์˜ํ˜ธ ๋‹˜์ด "ํ•œ ๋ฒˆ ErrorService ๋งŒ๋“ค์–ด๋‘๋ฉด ์–ด๋””์„œ๋“  ํ•œ ์ค„๋กœ ๋๋‚˜"๋ผ๊ณ  ํ–ˆ๋‹ค.

// ์—๋Ÿฌ ๋กœ๊น… ์„œ๋น„์Šค ์—ฐ๋™ ํŒจํ„ด (Sentry ๋“ฑ)
 
class ErrorService {
  static report(err, context = {}) {
    // 1. ์ฝ˜์†” ๋กœ๊ทธ (๊ฐœ๋ฐœ ํ™˜๊ฒฝ)
    if (process.env.NODE_ENV === "development") {
      console.error("[ErrorService]", err, context);
    }
 
    // 2. ์šด์˜ ์—๋Ÿฌ ์ถ”์  ์„œ๋น„์Šค (Sentry, Datadog ๋“ฑ)
    if (process.env.NODE_ENV === "production") {
      // Sentry.captureException(err, { extra: context });
    }
 
    // 3. ์šด์˜ ๊ฐ€๋Šฅํ•œ(Operational) ์—๋Ÿฌ๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ
    if (err instanceof AppError && err.isOperational) {
      return err.userMessage; // UI์— ํ‘œ์‹œ
    }
 
    return "์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."; // ๊ธฐ์ˆ ์  ์—๋Ÿฌ๋Š” ์ˆจ๊น€
  }
}
 
// React Error Boundary ํ™œ์šฉ (์ปดํฌ๋„ŒํŠธ ์—๋Ÿฌ)
class PostErrorBoundary extends React.Component {
  state = { hasError: false, error: null };
 
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
 
  componentDidCatch(error, errorInfo) {
    ErrorService.report(error, { componentStack: errorInfo.componentStack });
  }
 
  render() {
    if (this.state.hasError) {
      return <div>๊ฒŒ์‹œ๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</div>;
    }
    return this.props.children;
  }
}

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ์ปค์Šคํ…€ ์—๋Ÿฌ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค ๋•Œ Error.captureStackTrace๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ด์œ ๋Š”?

โœ… ์ •๋‹ต: ์Šคํƒ ํŠธ๋ ˆ์ด์Šค์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์‹ค์ œ ์œ„์น˜ ๋ฅผ ์ •ํ™•ํžˆ ๊ฐ€๋ฆฌํ‚ค๋„๋ก, ์—๋Ÿฌ ํด๋ž˜์Šค ์ƒ์„ฑ์ž ์ž์ฒด๊ฐ€ ์Šคํƒ์— ํฌํ•จ๋˜๋Š” ๊ฒƒ์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•จ.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • Error.captureStackTrace(this, this.constructor) ์—†์œผ๋ฉด, ์Šคํƒ ํŠธ๋ ˆ์ด์Šค์˜ ์ฒซ ๋ฒˆ์งธ ์ค„์ด AppError ์ƒ์„ฑ์ž๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค.
  • ์ด ํ˜ธ์ถœ๋กœ ์ƒ์„ฑ์ž๊ฐ€ ์Šคํƒ์—์„œ ์ œ๊ฑฐ๋˜์–ด, ์—๋Ÿฌ๊ฐ€ ์‹ค์ œ๋กœ throw๋œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์œ„์น˜ ๊ฐ€ ์Šคํƒ์˜ ์ฒซ ๋ฒˆ์งธ๋กœ ์˜จ๋‹ค.
  • V8 ์—”์ง„(Chrome, Node.js)์—์„œ๋งŒ ์ง€์›ํ•˜๋ฏ€๋กœ if (Error.captureStackTrace) ์ฒดํฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋Š” ๋””๋ฒ„๊น…์˜ ์ง€๋„๋‹ค. ์ง€๋„์˜ ์‹œ์ž‘์ ์ด ์—๋Ÿฌ ํด๋ž˜์Šค ์ƒ์„ฑ์ž๊ฐ€ ์•„๋‹ˆ๋ผ ์‹ค์ œ ๋ฌธ์ œ ๋ฐœ์ƒ ์ง€์  ์„ ๊ฐ€๋ฆฌ์ผœ์•ผ ํ•œ๋‹ค."

Q2. instanceof๋กœ ์—๋Ÿฌ ํƒ€์ž…์„ ๊ตฌ๋ถ„ํ•  ๋•Œ ์ฃผ์˜ํ•  ์ ์€?

โœ… ์ •๋‹ต: ์—๋Ÿฌ๋ฅผ ๋‹ค๋ฅธ realm(iframe, vm ๋ชจ๋“ˆ ๋“ฑ)์—์„œ ์ƒ์„ฑํ•œ ๊ฒฝ์šฐ instanceof๊ฐ€ false๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ minification์œผ๋กœ ํด๋ž˜์Šค ์ด๋ฆ„์ด ๋ฐ”๋€Œ๋ฉด err.name ๊ธฐ๋ฐ˜ ์ฒดํฌ๊ฐ€ ๋” ์•ˆ์ •์ ์ด๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

// instanceof ์ฒดํฌ (์ผ๋ฐ˜์ ์œผ๋กœ ์•ˆ์ „)
if (err instanceof NotFoundError) { ... }
 
// name ๊ธฐ๋ฐ˜ ์ฒดํฌ (๋” ๋ฐฉ์–ด์ )
if (err.name === "NotFoundError") { ... }
 
// ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ + type guard ํŒจํ„ด (๊ถŒ์žฅ)
function isNotFoundError(err: unknown): err is NotFoundError {
  return err instanceof NotFoundError || (err as any)?.name === "NotFoundError";
}
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "์ปค์Šคํ…€ ์—๋Ÿฌ๋Š” this.name = this.constructor.name์„ ์ƒ์„ฑ์ž์—์„œ ์„ค์ •ํ•˜๋ฉด minification ์ดํ›„์—๋„ err.name์œผ๋กœ ์‹๋ณ„ ๊ฐ€๋Šฅํ•˜๋‹ค."

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„ โ€” ๊ธด๊ธ‰ ๋””๋ฒ„๊น…

์˜์ˆ˜ PM์ด ๋‹ค๊ธ‰ํ•˜๊ฒŒ ์™ธ์ณค์Šต๋‹ˆ๋‹ค: "์˜์ฒ  ๋‹˜, ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด ์•„๋ฌด ๋ฐ˜์‘์ด ์—†์–ด์š”! ์ฝ˜์†”์—๋„ ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ๋‚˜์™€์š”. ์„œ๋ฒ„ ์—๋Ÿฌ์ธ์ง€ ํด๋ผ์ด์–ธํŠธ ์—๋Ÿฌ์ธ์ง€์กฐ์ฐจ ๋ชฐ๋ผ์š”!"

// ๐Ÿฃ ์˜์ฒ ์ด์˜ ์ฝ”๋“œ
async function deletePost(postId) {
  try {
    await api.delete(`/posts/${postId}`);
    toast.success("์‚ญ์ œ ์™„๋ฃŒ!");
  } catch {
    // ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•จ
  }
}

โœ… ์ •๋‹ต: catch ๋ธ”๋ก์ด ์™„์ „ํžˆ ๋น„์–ด์žˆ์–ด ์—๋Ÿฌ๋ฅผ ๋ฌด์‹œ(์‚ผํ‚ค๊ธฐ)ํ•˜๊ณ  ์žˆ๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ ์—๋Ÿฌ ํ”ผ๋“œ๋ฐฑ๋„ ์—†๊ณ , ๋กœ๊ทธ๋„ ์—†๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

// โœ… ๋ฆฌํŒฉํ† ๋ง๋œ ์ฝ”๋“œ
async function deletePost(postId) {
  try {
    await api.delete(`/posts/${postId}`);
    toast.success("์‚ญ์ œ ์™„๋ฃŒ!");
  } catch (err) {
    // 1. ์—๋Ÿฌ ํƒ€์ž…๋ณ„ ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€
    if (err instanceof AuthError) {
      toast.error("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.");
      redirectToLogin();
      return;
    }
    if (err instanceof ForbiddenError) {
      toast.error("์‚ญ์ œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.");
      return;
    }
    if (err instanceof NotFoundError) {
      toast.error("์ด๋ฏธ ์‚ญ์ œ๋œ ๊ฒŒ์‹œ๊ธ€์ž…๋‹ˆ๋‹ค.");
      return;
    }
 
    // 2. ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ โ€” ๋กœ๊น… + ์ผ๋ฐ˜ ๋ฉ”์‹œ์ง€
    console.error("๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ์‹คํŒจ:", err);
    ErrorService.report(err, { action: "deletePost", postId });
    toast.error("์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.");
  }
}
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๋นˆ catch ๋ธ”๋ก์€ ๋ฒ„๊ทธ์˜ ์›์ธ์„ ์ฐพ๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“œ๋Š” ๋งค์šฐ ์œ„ํ—˜ํ•œ ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ์˜ˆ์™ธ๋ฅผ ๋ฌด์‹œํ•˜๊ธฐ๋ณด๋‹ค๋Š” ์ตœ์†Œํ•œ ์—๋Ÿฌ ๋กœ๊น…์„ ๋‚จ๊ธฐ๊ณ , ์ด์ƒ์ ์œผ๋กœ๋Š” ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ๊ณผ ํ•จ๊ป˜ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค."

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜ ์ฝ”๋“œ๋ฆฌ๋ทฐ์—์„œ ๋นˆ catch ๋ธ”๋ก์œผ๋กœ ํ˜ผ๋‚ฌ๋Š”๋ฐ, ์ด๋ฒˆ ์ฑ•ํ„ฐ ๋ฐฐ์šฐ๋ฉด์„œ ๊ทธ๊ฒŒ ์–ผ๋งˆ๋‚˜ ์œ„ํ—˜ํ•œ ํŒจํ„ด์ธ์ง€ ์ œ๋Œ€๋กœ ์ดํ•ดํ–ˆ๋‹ค. "์—๋Ÿฌ๋ฅผ ์กฐ์šฉํžˆ ์‚ผํ‚ค๋ฉด ๋ฒ„๊ทธ๊ฐ€ ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค" โ€” ์ด ๋ง์ด ๋จธ๋ฆฟ์†์—์„œ ๊ณ„์† ๋งด๋ˆ๋‹ค.

์ปค์Šคํ…€ ์—๋Ÿฌ ํด๋ž˜์Šค๋„ ์ฒ˜์Œ์—๋Š” "๊ตณ์ด ํด๋ž˜์Šค๊นŒ์ง€ ๋งŒ๋“ค์–ด์•ผ ํ•ด?" ์‹ถ์—ˆ๋Š”๋ฐ, err.userMessage, err.statusCode๋กœ ์—๋Ÿฌ ํƒ€์ž…์— ๋งž๋Š” ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฑธ ์ง์ ‘ ์ฝ”๋“œ๋กœ ๋ณด๋‹ˆ๊นŒ ๋‚ฉ๋“์ด ๋๋‹ค. Failed to fetch ํ•˜๋‚˜๋กœ ์•„๋ฌด๊ฒƒ๋„ ๋ชป ํ•˜๋˜ ๊ฒƒ๊ณผ ๋น„๊ต๊ฐ€ ๋œ๋‹ค.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "์—๋Ÿฌ ์ฒ˜๋ฆฌ๋Š” '์˜ˆ์™ธ๋ฅผ ๋ง‰๋Š” ๊ฒƒ'์ด ์•„๋‹ˆ๋ผ '์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ์˜ ๋‹ค์Œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ'์ด๋‹ค. ๋นˆ catch ๋ธ”๋ก์€ ์ด ์˜ˆ์™ธ ์ƒํ™ฉ์— ๋ˆˆ์„ ๊ฐ€๋ฆฌ๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค."

์˜ํ˜ธ ๋‹˜์ด "์—๋Ÿฌ๋„ ์„ค๊ณ„๊ฐ€ ํ•„์š”ํ•ด"๋ผ๊ณ  ํ–ˆ๋Š”๋ฐ, ๊ทธ ๋ง์ด ์˜ค๋Š˜ ํ•˜๋ฃจ ๋‚ด๋‚ด ์ƒ๊ฐ๋‚ฌ๋‹ค. ๋‹ค์Œ ์ฃผ์—๋Š” Map/Set ์ฑ•ํ„ฐ์ธ๋ฐ, ์ž๋ฃŒ๊ตฌ์กฐ๋„ ์ž˜ ์จ์•ผ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค. ํ‡ด๊ทผํ•˜๊ณ  ์‚ฐ์ฑ… ํ•œ ๋ฐ”ํ€ด ๋Œ๊ณ  ์ž์•ผ์ง€.


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ