에러핸들링

김수정·2020년 5월 4일
0

js 데이터 다루기

목록 보기
5/6

의미

스크립트에 오류가 발생하더라도, 스크립트가 죽고 사이트가 멈추는 게 아니라 오류를 다루는 코드로 넘어간 뒤, 다음 코드를 실행하는 것을 말합니다.
이러면 오류가 나더라도 사이트가 멈추는 것을 방지할 수 있습니다.

문법

try문 안에서 에러가 나지 않으면 catch문은 실행되지 않으며, try문 안에서 에러 발생시 catch문으로 바로 넘어갑니다.

  • try catch문은 자바스크립트의 문법 오류를 잡는 것이 아니라 런타임 에러를 잡는 것입니다.
  • setTimeout처럼 스케줄 된 코드에서 발생한 에러는 잡아낼 수 없습니다. 하고 싶다면 콜백함수 안에서 try-catch문을 구현하세요.
  • finally는 오류가 나던 나지 않던 실행되는 영역이며, return문을 만나 함수가 종료되어도 그 직전에 호출됩니다.
  • catch와 finally는 선택적인 항목입니다.
try {

  // 코드...

} catch (err) {

  // 에러 핸들링

} finally {
  // 항상 실행
}

에러 객체

에러 발생 시 에러 상세내용이 담긴 객체입니다. catch문에서 매개변수로 접근 가능합니다.

  • name : 에러 이름
  • message : 에러 내용
  • stack : 비표준이지만 많이 쓰임. 현재 호출 스택
try {
  lalala; // 에러, 변수가 정의되지 않음!
} catch(err) {
  alert(err.name); // ReferenceError
  alert(err.message); // lalala is not defined
  alert(err.stack); // ReferenceError: lalala is not defined at ... (호출 스택)

  // 에러 전체를 보여줄 수도 있습니다.
  // 이때, 에러 객체는 "name: message" 형태의 문자열로 변환됩니다.
  console.dir(err); // ReferenceError: lalala is not defined
}

에러 처리하기

에러 발생

let error = new Error("이상한 일이 발생했습니다. o_O");

alert(error.name); // Error
alert(error.message); // 이상한 일이 발생했습니다. o_O

에러 처리

try안에서 어떤 에러가 나든 다 catch문으로만 가므로 에러를 분기하려면 아래와 같이 합니다.

function readData() {
  let json = '{ "age": 30 }';

  try {
    // ...
    blabla(); // 에러!
  } catch (e) {
    // ...
    if (e.name != 'SyntaxError') {
      throw e; // 알 수 없는 에러 다시 던지기
    }
  }
}

try {
  readData();
} catch (e) {
  alert( "External catch got: " + e ); // 에러를 잡음
}

전역 catch

브라우저의 경우 window.onerror를 이용해 에러를 처리할 수 있습니다. 그러나 전역에서 죽어버린 스크립트 복구가 사실상 불가능하여 그 방식으로는 잘 사용하지 않습니다.

window.onerror = function(message, url, line, col, error) {
  // ...
};

커스텀 에러

보다 명확한 에러를 주기 위해서 직접 만들 수 있습니다.

  • 직접 에러 클래스를 만들 경우, message, name, 가능하다면 stack 프로퍼티를 지원해야 합니다.
    httpError의 경우엔 statusCode도 지정 고려.
  • throw의 인수엔 아무런 제약이 없지만 에러의 제공 환경을 이용하기 위해 Error객체를 활용하여 에러를 만듭니다.

에러 만들기

에러 객체를 상속받아 만듭니다.
에러 객체를 상속받으면 instanceof를 사용할 수 있고, 에러를 다시 던지는 것도 가능합니다.

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

// 사용법
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new ValidationError("No field: age");
  }
  if (!user.name) {
    throw new ValidationError("No field: name");
  }

  return user;
}

// try..catch와 readUser를 함께 사용한 예시

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
    alert("Invalid data: " + err.message); // Invalid data: No field: name
  } else if (err instanceof SyntaxError) { // (*)
    alert("JSON Syntax Error: " + err.message);
  } else {
    throw err; // 알려지지 않은 에러는 재던지기 합니다. (**)
  }
}

한 번 더 상속받은 에러

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

class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super("No property: " + property);
    this.name = this.constructor.name; // class이름으로 지정됨.
    this.property = property;
  }
}

// 사용법
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new PropertyRequiredError("age");
  }
  if (!user.name) {
    throw new PropertyRequiredError("name");
  }

  return user;
}

// try..catch와 readUser를 함께 사용하면 다음과 같습니다.

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
    alert("Invalid data: " + err.message); // Invalid data: No property: name
    alert(err.name); // PropertyRequiredError
    alert(err.property); // name
  } else if (err instanceof SyntaxError) {
    alert("JSON Syntax Error: " + err.message);
  } else {
    throw err; // 알려지지 않은 에러는 재던지기 합니다.
  }
}

에러 감싸기

에러가 나는 것을 감지하는 대표 에러클래스를 하나 만들고, 각각의 에러를 보여주는 형식을 써서 외부 코드에서는 여러 에러를 분기처리할 필요가 없어 깔끔하게 작성할 수 있습니다.

class ReadError extends Error {
  constructor(message, cause) {
    super(message);
    this.cause = cause;
    this.name = 'ReadError';
  }
}

class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }

function validateUser(user) {
  if (!user.age) {
    throw new PropertyRequiredError("age");
  }

  if (!user.name) {
    throw new PropertyRequiredError("name");
  }
}

function readUser(json) {
  let user;

  try {
    user = JSON.parse(json);
  } catch (err) {
    if (err instanceof SyntaxError) {
      throw new ReadError("Syntax Error", err);
    } else {
      throw err;
    }
  }

  try {
    validateUser(user);
  } catch (err) {
    if (err instanceof ValidationError) {
      throw new ReadError("Validation Error", err);
    } else {
      throw err;
    }
  }

}

try {
  readUser('{잘못된 형식의 json}');
} catch (e) {
  if (e instanceof ReadError) {
    alert(e);
    // Original error: SyntaxError: Unexpected token b in JSON at position 1
    alert("Original error: " + e.cause);
  } else {
    throw e;
  }
}
profile
정리하는 개발자

0개의 댓글