자바스크립트 교재 · 13편 / 26편

에러 처리 — try·catch·finally·throw

예상 못한 일이 일어났을 때 코드가 무너지지 않게 하는 도구.

기초읽는 시간 7분2026-05-17
try·catch·finally 블록 흐름이 그림으로 정리된 일러스트

네트워크가 끊기고, 사용자가 이상한 값을 넣고, 파일이 없고 — 코드 작성자가 예상하지 못한 일은 항상 일어납니다. 그걸 우아하게 다루는 표준 도구가 try·catch·throw. 13편은 입문~기초 파트의 마지막 — 다음부터는 중급 파트(DOM·비동기·API)로 들어갑니다.

왜 에러 처리가 필요한가

// ❌ 에러 한 줄이 전체 페이지 죽임
const data = JSON.parse(userInput);   // 입력이 잘못된 JSON 이면 SyntaxError
console.log(data.name);                // 이 줄도, 그 아래도 모두 실행 안 됨

// ✅ try/catch 로 격리
let data;
try {
  data = JSON.parse(userInput);
} catch (err) {
  console.error("JSON 파싱 실패:", err.message);
  data = {};   // 기본값으로 fallback
}
console.log(data.name);                // 정상 실행

try·catch·finally 기본

try {
  // 1) 위험할 수 있는 코드
  risky();
} catch (err) {
  // 2) 에러 발생 시만 실행
  console.error(err);
} finally {
  // 3) 성공·실패 무관하게 항상 실행 (정리·로깅)
  cleanup();
}

finally 의 진가. 파일 닫기, DB 연결 해제, 로딩 스피너 끄기 — 성공해도 실패해도 반드시 해야 하는 정리 작업에 씁니다. catch 안에서 또 throw 해도 finally 는 실행됨.

Error 객체 — 기본 구조

const err = new Error("뭔가 잘못됨");

err.name      // "Error"
err.message   // "뭔가 잘못됨"
err.stack     // 호출 스택 문자열 (디버깅의 핵심)

// 표준 내장 에러 클래스들
new TypeError("문자열 기대했는데 숫자");
new RangeError("범위 벗어남");
new SyntaxError("문법 오류");
new ReferenceError("선언 안 된 변수");
new URIError("encodeURI 실패");

throw — 직접 에러 발생

function divide(a, b) {
  if (typeof a !== "number" || typeof b !== "number") {
    throw new TypeError("숫자여야 함");
  }
  if (b === 0) {
    throw new RangeError("0 으로 나눌 수 없음");
  }
  return a / b;
}

try {
  divide(10, 0);
} catch (err) {
  console.error(err.name, err.message);
  // "RangeError" "0 으로 나눌 수 없음"
}

문자열 throw 금지. throw "에러" 같은 패턴 가끔 보이지만 — stack trace 가 사라집니다. 디버깅이 거의 불가능해져요. 항상 throw new Error(...) 또는 표준 서브클래스.

커스텀 에러 클래스

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

class NotFoundError extends Error {
  constructor(resource, id) {
    super(`${resource} #${id} not found`);
    this.name = "NotFoundError";
    this.resource = resource;
    this.id = id;
  }
}

// 사용
function findUser(id) {
  const u = users.get(id);
  if (!u) throw new NotFoundError("User", id);
  return u;
}

try {
  findUser(999);
} catch (err) {
  if (err instanceof NotFoundError) {
    // 404 처리
  } else if (err instanceof ValidationError) {
    // 400 처리
  } else {
    // 알 수 없는 에러 — 다시 던지기
    throw err;
  }
}

에러 다시 던지기 (re-throw) — 핵심 패턴

try {
  loadConfig();
} catch (err) {
  if (err.code === "ENOENT") {
    // 파일 없으면 기본 설정으로
    return defaultConfig();
  }
  // 그 외 에러는 호출자가 처리하도록 다시 던짐
  throw err;
}

한 줄 원칙. "내가 모르는 에러는 잡지 마라." 모르는 에러를 catch 만 하고 무시하면(catch {}) 진짜 버그가 조용히 사라집니다. 잡을 수 있는 종류만 잡고 나머지는 던지기.

비동기 에러 — async/await 와 함께

// fetch 같은 비동기 — 똑같이 try/catch
async function loadUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error("로드 실패:", err);
    return null;
  }
}

// Promise.then 으로는 .catch
fetch("/api/x")
  .then(r => r.json())
  .catch(err => console.error(err));

비동기·Promise 의 자세한 패턴은 15·16편(비동기 입문·Promise/async).

unknown 으로 받기 (TS) / instanceof 좁히기

try {
  doSomething();
} catch (err) {
  // err 의 타입은 unknown (또는 any)
  // 어떤 게 던져졌는지 모름 → 검사
  if (err instanceof TypeError) {
    console.error("타입 에러:", err.message);
  } else if (err instanceof Error) {
    console.error("일반 에러:", err.message);
  } else {
    console.error("이상한 것 던져짐:", err);
  }
}

에러 처리 안티패턴 5가지

1. 빈 catch. catch {} 또는 catch(e) {} — 에러가 조용히 사라져 진짜 버그를 숨김.

2. catch 에서 console.log 만. 에러 발생을 사용자/모니터링에 알리지 않으면 안 일어난 것과 같음.

3. 모든 함수에 try/catch. 어차피 다시 던질 거면 가장 위(엔드포인트·이벤트 핸들러) 에서 한 번에 처리.

4. 에러를 boolean 으로 변환. try { ... return true } catch { return false } 같은 패턴은 어떤 에러인지 정보를 다 버림.

5. 너무 일반적인 메시지. "Error occurred" 같은 메시지는 도움 안 됨. 무엇이·어디서·왜 가 보이게.

전역 에러 핸들러 — 마지막 그물

// 브라우저
window.addEventListener("error", (event) => {
  console.error("처리 안 된 에러:", event.error);
  // Sentry·DataDog 같은 모니터링으로 전송
});
window.addEventListener("unhandledrejection", (event) => {
  console.error("처리 안 된 Promise:", event.reason);
});

// Node
process.on("uncaughtException", (err) => { ... });
process.on("unhandledRejection", (reason) => { ... });

14편 — DOM 조작 (중급 시작)

querySelector · 이벤트 리스너 · 이벤트 위임. 입문~기초가 끝나고 진짜 웹페이지를 만집니다.

📚 쉽게 배우는 자바스크립트 교재
이전: 12편 스코프 · 현재: 13편 (기초 마지막) · 다음 → 14편 DOM (중급 시작) · 진행: 13/26

© 2026 주나이테크(주) @JUNAITECH