Learning
레슨 11 / 12·6개 토픽

에러 처리와 Set/Map

try / catch / finally

try 블록에서 에러가 발생하면 catch로 잡아서 처리합니다. finally는 에러 발생 여부와 관계없이 항상 실행됩니다. 에러를 적절히 처리하면 프로그램이 비정상 종료되지 않습니다.

javascript
// 기본 try / catch / finally
try {
  const data = JSON.parse("잘못된 JSON");
} catch (error) {
  console.log(error.name);    // "SyntaxError"
  console.log(error.message); // "Unexpected token ..."
} finally {
  console.log("항상 실행됩니다");
}

// 에러 던지기
function divide(a, b) {
  if (b === 0) {
    throw new Error("0으로 나눌 수 없습니다");
  }
  return a / b;
}

try {
  console.log(divide(10, 0));
} catch (e) {
  console.log(e.message); // "0으로 나눌 수 없습니다"
}

내장 에러 타입

  • TypeError — 값의 타입이 잘못되었을 때 (null.property, "str"())
  • ReferenceError — 정의되지 않은 변수를 참조할 때
  • SyntaxError — 코드 문법이 잘못되었을 때 (JSON.parse 실패 등)
  • RangeError — 값이 허용 범위를 벗어날 때 (new Array(-1))
  • URIError — URI 인코딩/디코딩 오류
  • EvalError — eval() 관련 오류 (현대 JS에서는 거의 발생하지 않음)
javascript
// 에러 타입별 분기 처리
function processInput(input) {
  try {
    const data = JSON.parse(input);
    return data.value.toUpperCase();
  } catch (e) {
    if (e instanceof SyntaxError) {
      console.log("JSON 파싱 실패:", e.message);
    } else if (e instanceof TypeError) {
      console.log("속성 접근 실패:", e.message);
    } else {
      throw e;  // 알 수 없는 에러는 다시 던지기
    }
  }
}

processInput("유효하지 않은 JSON");    // "JSON 파싱 실패: ..."
processInput('{"noValue": 1}');       // "속성 접근 실패: ..."

커스텀 에러 클래스

javascript
// 커스텀 에러 만들기
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

class NotFoundError extends Error {
  constructor(resource) {
    super(resource + "을(를) 찾을 수 없습니다");
    this.name = "NotFoundError";
    this.resource = resource;
  }
}

// 사용 예시
function validateAge(age) {
  if (typeof age !== "number") {
    throw new ValidationError("age", "숫자여야 합니다");
  }
  if (age < 0 || age > 150) {
    throw new ValidationError("age", "0~150 범위여야 합니다");
  }
  return true;
}

try {
  validateAge("스물다섯");
} catch (e) {
  if (e instanceof ValidationError) {
    console.log(e.field + ": " + e.message);
    // "age: 숫자여야 합니다"
  }
}

Set 심화

Set은 중복 없는 값의 컬렉션입니다. 값의 존재 확인(has)이 배열의 includes보다 훨씬 빠르며(O(1) vs O(n)), 집합 연산에 유용합니다.

javascript
// Set 기본 메서드
const tags = new Set();
tags.add("javascript");
tags.add("web");
tags.add("javascript");  // 중복 무시
console.log(tags.size);            // 2
console.log(tags.has("web"));      // true
tags.delete("web");
console.log(tags.size);            // 1

// 배열 → Set → 배열 (중복 제거)
const numbers = [1, 2, 2, 3, 3, 3, 4];
const unique = [...new Set(numbers)]; // [1, 2, 3, 4]

// Set 순회
const fruits = new Set(["사과", "바나나", "체리"]);
for (const fruit of fruits) {
  console.log(fruit);
}
fruits.forEach(fruit => console.log(fruit));

// 집합 연산
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);

// 합집합
const union = new Set([...setA, ...setB]);
// {1, 2, 3, 4, 5, 6}

// 교집합
const intersection = new Set([...setA].filter(x => setB.has(x)));
// {3, 4}

// 차집합
const difference = new Set([...setA].filter(x => !setB.has(x)));
// {1, 2}

Map 심화

Map은 키-값 쌍의 컬렉션으로, 일반 객체와 달리 어떤 타입이든 키로 사용 가능합니다. 삽입 순서가 보장되고, size로 바로 크기를 확인할 수 있습니다.

javascript
// Map 기본 메서드
const userRoles = new Map();
userRoles.set("김철수", "admin");
userRoles.set("이영희", "editor");
userRoles.set("박민수", "viewer");

console.log(userRoles.get("김철수")); // "admin"
console.log(userRoles.has("이영희")); // true
console.log(userRoles.size);          // 3
userRoles.delete("박민수");

// Map 순회
for (const [name, role] of userRoles) {
  console.log(name + ": " + role);
}

// 객체를 키로 사용 (일반 객체로는 불가능)
const elementCounts = new Map();
const div = document.createElement("div");
const span = document.createElement("span");
elementCounts.set(div, 5);
elementCounts.set(span, 3);
console.log(elementCounts.get(div)); // 5

// Map vs Object 비교
const obj = {};
obj["key"] = "value";
console.log(Object.keys(obj).length); // size 직접 확인 불가

const map = new Map();
map.set("key", "value");
console.log(map.size);  // 1 — 바로 확인 가능

WeakMap과 WeakSet

WeakMapWeakSet은 키(WeakMap) 또는 값(WeakSet)으로 객체만 허용하며, 다른 곳에서 참조가 없어지면 가비지 컬렉션됩니다. 메모리 누수 방지에 유용합니다.

javascript
// WeakMap — DOM 요소에 데이터 연결 (메모리 누수 방지)
const metadata = new WeakMap();

function trackElement(element, info) {
  metadata.set(element, info);
}

let btn = document.createElement("button");
trackElement(btn, { clicks: 0, created: Date.now() });
console.log(metadata.get(btn)); // { clicks: 0, created: ... }
btn = null;  // 참조 제거 → WeakMap에서도 자동 정리됨

// WeakSet — 객체 방문 여부 추적
const visited = new WeakSet();

function processNode(node) {
  if (visited.has(node)) {
    return;  // 이미 방문한 노드 스킵
  }
  visited.add(node);
  console.log("처리:", node.id);
}

const nodeA = { id: "A" };
const nodeB = { id: "B" };
processNode(nodeA); // "처리: A"
processNode(nodeA); // (스킵)
processNode(nodeB); // "처리: B"
💡

WeakMap/WeakSet은 순회(iteration)가 불가능하고 size 속성이 없습니다. 이는 가비지 컬렉션 시점을 예측할 수 없기 때문입니다. 캐시, DOM 메타데이터, 순환 참조 방지 등에 활용하세요.