# 에러 핸들링
# try..catch 와 에러 핸들링
에러의 원인
- 실수
- 예기치 못한 사용자 입력
- 잘못된 서버 응답 ....
에러 발생의 결과
- 스크립트가 즉시 중단
- 콘솔에 에러가 출력
try..catch
- 스크립트가 죽는 것을 방지
- 에러를 catch 해서 더 합당한 무언가를 할 수 있게 한다.
# 런타임 에러(예외 exception) 에서만 동작
- 실행가능한 runnable 코드 : 유효한 자바스크립트 코드
- 중괄호의 짝이 안맞는 것처럼 문법적으로 잘못된 경우 try..catch 가 동작하지 않는다.
# 동기적으로 동작한다.
setTimeout 처럼 비동기 함수에서 발생한 예외는 잡아낼 수 없다.
# 에러 객체
- 에러가 발생하면 자바스크립트는 에러 상세내용이 담긴 객체를 생성
- catch 블록에 에러 객체를 인수로 전달
# 에러 객체의 주요 프로퍼티
- name
- 에러 이름
- 'ReferenceError' : 정의되지 않은 변수 때문에 발생한 에러
- 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" 형태의 문자열로 변환됩니다.
alert(err); // ReferenceError: lalala is not defined
}
2
3
4
5
6
7
8
9
10
11
# 선택적 catch 바인딩
에러에 대한 자세한 정보가 필요하지 않을 때, catch 에서 인수를 생략
try {
// ...
} catch { // <-- (err) 없이 쓸 수 있음
// ...
}
2
3
4
5
구식 브라우저에는 폴리필이 필요
# try..catch 사용하기
JSON.parse
- 인자에 잘못된 형식의 json 이 들어온 경우 JSON.parse 는 에러를 만든다.
const json = "{ bad json }";
try {
let user = JSON.parse(json); // <-- 여기서 에러가 발생하므로
alert( user.name ); // 이 코드는 동작하지 않습니다.
} catch (e) {
// 에러가 발생하면 제어 흐름이 catch 문으로 넘어옵니다.
alert( "데이터에 에러가 있어 재요청을 시도합니다." );
alert( e.name );
alert( e.message );
}
2
3
4
5
6
7
8
9
10
11
12
13
catch 블록에서 할 수 있는 일
- 새로운 네트워크 요청 보내기
- 사용자에게 대안 제안하기
- 로깅 장치에 에러 정보 보내기
# 직접 에러를 만들어서 던지기
# throw 연산자
에러를 생성한다.
throw <error object>
에러 객체
- 숫자, 문자열 같은 원시형 자료를 포함한 무엇이든 가능
- 내장 에러와의 호환을 위해 : { name, message } 프로퍼티 권장
표준 에러 객체 관련 생성자
- Error, SyntaxError, ReferenceError, TypeError
let error = new Error(message); error = new SyntaxError(message); error = new ReferenceError(message); error = new Error("이상한 일이 발생했습니다. o_O"); alert(error.name); // Error alert(error.message); // 이상한 일이 발생했습니다. o_O
1
2
3
4
5
6
7
name 프로퍼티가 없으면 예외 처리
const json = '{ "age": 30 }'; // 불완전한 데이터
try {
let user = JSON.parse(json); // <-- 에러 없음
alert( user.name ); // 이름이 없습니다!
} catch (e) {
alert( "실행되지 않습니다." );
}
2
3
4
5
6
7
8
9
10
const json = '{ "age": 30 }'; // 불완전한 데이터
try {
const user = JSON.parse(json); // <-- 에러 없음
if (!user.name)
throw new SyntaxError("불완전한 데이터: 이름 없음"); // (*)
alert( user.name );
} catch(e) {
alert( "JSON Error: " + e.message ); // JSON Error: 불완전한 데이터: 이름 없음
}
2
3
4
5
6
7
8
9
10
11
12
# 에러 다시 던지기
목표: user 앞에 let 을 붙이지 않아 발생하는 에러를 처리
문제: Json SyntaxError 만 처리해주는 alert 만 존재한다.
'use strict';
const json = '{ "age": 30 }'; // 불완전한 데이터
try {
user = JSON.parse(json); // <-- user 앞에 let을 붙이는 걸 잊었네요.
// ...
} catch(err) {
alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
// (실제론 JSON Error가 아닙니다.)
}
2
3
4
5
6
7
8
9
10
11
user 앞에 let 을 붙이지 않아 발생하는 ReferenceError 는 외부에서 처리하도록 catch 블록에서 throw 를 한번 더 해준다.
const json = '{ "age": 30 }'; // 불완전한 데이터
try {
user = JSON.parse(json);
if (!user.name)
throw new SyntaxError("불완전한 데이터: 이름 없음");
blabla(); // 예상치 못한 에러
alert( user.name );
} catch(e) {
if (e instanceof SyntaxError) {
alert( "JSON Error: " + e.message );
} else {
throw e; // 에러 다시 던지기 (*)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
catch 안의 throw e
- try..catch 밖의 try..catch 문에 걸리거나
function readData() {
const json = '{ "age": 30 }';
try {
// ...
blabla(); // 에러!
} catch (e) {
// ...
if (!(e instanceof SyntaxError)) {
throw e; // 알 수 없는 에러 다시 던지기
}
}
}
try {
readData();
} catch (e) {
alert( "External catch got: " + e ); // 에러를 잡음
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 스크립트가 죽음
# try..catch..finally
function x() {
try {
// ... 코드를 실행 ...
return;
} catch(e) {
//... 에러 핸들링 ...
} finally {
// ... 항상 실행 ...
// 작업을 초기화 하는 코드
console.log('execution')
}
}
2
3
4
5
6
7
8
9
10
11
12
function x() {
try {
// ... 코드를 실행 ...
} catch(e) {
//... 에러 핸들링 ...
}
console.log('execution')
}
2
3
4
5
6
7
8
두 예제의 차이점
- try 에서 return 이 발생하면 두 번째 예제의 execution 은 출력되지 않는다.
# try..finally
- 안에서 에러를 처리하고 싶지 않을 때
- 시작한 프로세스가 마무리 되었는지 확실히 하고 싶은 경우
function func() {
// 무언가를 측정하는 경우와 같이 끝맺음이 있어야 하는 프로세스
try {
// ...
} finally {
// 스크립트가 죽더라도 완료됨
}
}
2
3
4
5
6
7
8
- try 안에서 발생한 에러는 외부에서 catch 해주어야 한다.
- finally 는 실행흐름이 함수를 떠나기 전에 실행된다.
# 전역 catch
호스트 환경에 따라 다르다.
node.js process.on('uncaughtException')
브라우저 window.onerror
<script>
window.onerror = function(message, url, line, col, error) {
alert(`${message}\n At ${line}:${col} of ${url}`);
// message 에러 메시지
// url 에러가 발생한 스크립트의 URL
// line, col 에러가 발생한 곳의 줄과 열 번호
// error 에러 객체
};
function readData() {
badFunc(); // 에러가 발생한 장소
}
readData();
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
- 죽어버린 스크립트를 복구하려는 목적보다 개발자에게 에러 메시지를 보내는 용도로 사용한다.
# 커스텀 에러와 확장
Error 를 상속받아 커스텀에러를 만드는 것의 장점
- obj instanceof Error 를 사용하여 에러 식별 가능
- 등 Error 객체의 장점들을 사용할 수 있다.
# 에러 확장하기
자바스크립트 자체 내장 에러 클래스 Error 의 '슈도 코드'
class Error {
constructor(message) {
this.message = message;
this.name = "Error"; // (name은 내장 에러 클래스마다 다릅니다.)
this.stack = <call stack>; // stack은 표준은 아니지만, 대다수 환경이 지원합니다.
}
}
2
3
4
5
6
7
Error 를 상속받은 ValidationError
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function test() {
throw new ValidationError('에러 발생!');
}
try {
test();
} catch (err) {
alert(err.message); // 에러 발생!
alert(err.name); // ValidationError
alert(err.stack); // 각 행 번호가 있는 중첩된 호출들의 목록
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
readUser(json)
라는 함수를 만들어보자
- SyntaxError : JSON 형식인지 검사
- 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 {
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; // 알려지지 않은 에러는 재던지기 합니다. (**)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 더 깊게 상속하기
Validation Error 에서 프로퍼티를 확장하여 PropertyRequiredError
를 만들어 보자
- 필수 프로퍼티가 없는 경우 예외 처리
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("No property: " + property);
this.name = "PropertyRequiredError";
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 {
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; // 알려지지 않은 에러는 재던지기 합니다.
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
err.name == "SyntaxError"
보다 err instanceof SyntaxError
를 선호하는 이유
- ValidationError 를 확장한 PropertyRequiredError 같은 새로운 확장 에러를 만들 때, instanceof 는 새로운 상속 클래스에서도 동작한다.
Error 객체의 name 자동할당
class MyError extends Error {
constructor (message) {
super(message);
this.name = this.constructor.name;
}
}
class ValidationError extends MyError { ... }
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("No property: " + property);
this.property = property;
}
}
alert( new PropertyRequiredError("field").name ); // PropertyRequiredError
2
3
4
5
6
7
8
9
10
11
12
13
14
# 예외 감싸기
readUser
가 확장되면 커스텀 에러 클래스를 더 만들어야 한다.
try {
//...
readUser() // 잠재적 에러 발생처
// ...
} catch (err) {
if (err instanceof ValidationError) {
// validation 에러 처리
} else if (err instanceof SyntaxError) {
// 문법 에러 처리
} else {
throw err; // 알 수 없는 에러는 다시 던지기 함
}
}
2
3
4
5
6
7
8
9
10
11
12
13
하지만 실제로 필요로 하는 정보는 '데이터를 읽을 때' 에러 발생 여부이다.
예외 감싸기
- 널리 알려진 예외처리 기술
- 모든 에러를 종류별로 처리 하지 않는다.
- 모든 에러를 포함할 수 있는 추상 에러를 하나 만든다.
- 에러가 발생하면 추상 에러를 던지도록 한다.
에러 발생 여부를 가려내기 위해 예외 감싸기 (wrapping exception) 을 해보자
예외 감싸기의 장점
- readUser 를 호출하는 코드에선 ReadError 만 확인
- 추가 정보가 필요한 경우엔 cause 프로퍼티를 확인
ReadError
데이터 읽기와 같은 포괄적인 에러를 대변하는 클래스class ReadError extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = 'ReadError'; } }
1
2
3
4
5
6
7- ValidationError, SyntaxError 는 readUser 내부에서 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; } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 - ReadError 객체의
cause
프로퍼티엔 실제 에러에 대한 참조 저장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; } }
1
2
3
4
5
6
7
8
9
10
11
# Reference
- https://ko.javascript.info/try-catch
- https://ko.javascript.info/custom-errors