에러 없는 코드를 작성하는 것은 불가능하다. 즉 에러는 언제나 발생할 수 있다. 에러가 발생하면 프로그램은 강제 종료되므로 이를 피하기 위해 발생한 에러에 예외 처리를 하여 대처할 수 있다.
오류의 종류에는 구문 오류(Syntax Errors, Parsing errors), 런타임 오류(Runtime Errors, Exceptions), 논리적 오류(Logical Errors)가 있다.
구문 오류는 JavaScript의 인터프리트 시간에 발생한다. 예를 들어 다음 코드는 닫는 괄호가 없기 때문에 구문 오류가 발생한다.
console.log('hello world' //SyntaxError: missing ) after argument list
런타임 오류는 코드 실행 중에 발생한다. 구문 오류와는 달리 코드에는 오류가 없다.
예를 들어 다음 코드는 구문에 오류는 없지만 정의하지 않은 식별자를 호출하였기 때문에 런타임 오류가 발생한다.
foo(2,2); // ReferenceError: sum is not defined
논리적 오류는 프로그래밍 중 논리적인 실수가 있어 예상한 결과를 얻지 못알 때 발생한다. 인터프리터에 감지되지 않으며 결과가 나온 후에야 오류를 확인할 수 있기 때문에 추적하기 가장 어려운 유형이라 할 수 있다.
Error 생성자 함수는 에러 메세지를 전달하는 message 프로퍼티와 에러가 생성된 콜 스택의 호출 정보를 제공하는 stack 프로퍼티 그리고 에러 이름을 제공하는 name 프로퍼티가 있다.
또한 JavaScript는 Error 생성자 함수에 7가지의 에러 오브젝트를 제공하는데 일반적 에러 객체인 Error, 문법에 맞지 않는 문을 해석할 때 발생하는 에러 객체인 SyntaxError, 참조할 수 없는 식별자를 참조했을 때 발생하는 에러 객체인 ReferenceError, 피연산자 또는 인수의 데이터 타입이 유효하지 않을 때 발생하는 에러 객체인 TypeError, 숫자값의 허용 범위를 벗어났을 때 발생하는 에러 객체인 RangeError, encodeURI 또는 decodeURI 함수에 부적절한 인수를 제공했을 때 발생하는 에러 객체인 URIError, eval 함수에서 발생하는 에러 객체인 EvalError가 있다.
1 @ 1; // SyntaxError: Invalid or unexpected token
foo(); // ReferenceError: foo is not defind
null.foo; // TypeError: Cannot read property 'foo' of null
new Array(-1); // RangeError: Invalid array length
decodeURIComponent(%); // URIError: URI malfromed
try...catch...finally 문을 실행하면 먼저 try 코드 블록이 실행된다. 이후 try 코드 블록의 문에서 에러가 발생하면 발생한 에러는 catch 문의 err에 전달되고 catch 코드 블록이 실행된다. finally 코드 블록은 반드시 한 번 실행되며 불필요하다면 생략할 수 있다. catch 구문 또한 생략 가능하지만, try 구문 혼자서는 별 의미가 없으므로 사용하지 않는다.
try{
// 실행할 코드 (에러가 발생할 가능성이 있는 코드)
foo();
} catch (err) {
// try 코드 블록에서 에러가 발생하면 이 코드 불록의 코드가 실행된다.
// err에는 try 코드 블록에서 발생한 Error 객체가 전달된다.
console.error(err); // ReferenceError: foo is not defined
} finally {
// 에러 발생과 상관없이 이 코드가 실행된다.
}
Error 생성자 함수로 에러 객체를 생성한다고 에러가 발생하는 것은 아니다. 에러 생성과 에러 발생은 의미가 다르다고 할 수 있다.
에러를 발생시키려면 try 코드 블록에서 throw 문으로 에러 객체를 던져야 한다.
try {
// throw 표현식;
throw new Error('something wrong');
} catch (err) {
console.log(err) // Error: something wrong
}
throw 문의 표현식은 어떤 값이라도 상관없지만 일반적으로 에러 객체를 지정한다.
비동기 상황에서는 try...catch...finally 구문이 동작하지 않는다. 코드가 바로 콜스택에 들어가는 것이 아니라 작업 큐에서 대기를 하기 때문이다. 따라서 예외가 발생하는 시점과 try가 싸고 있는 시간대가 달라 try 구문이 작동하지 않는다.
Promise가 포함되어 있는 함수 실행부 뒤에 .catch() 메서드를 사용하여 에외 처리를 할 수 있다. .catch() 메서드는 프로미스가 reject 상태일 경우에만 호출된다.
function wait(sec) {
return new Promise((resolve, reject) => {
setTimeout(() => { reject('error!');
}, sec * 1000);
});
}
wait(1).catch(e => { console.log('1st catch ', e); });
// 1st catch error!
.then() 메서드는 .catch 메서드와 비슷하게 동작하는데, 첫 번째 콜백 함수는 비동기 처리가 성공했을 때, 두 번째 콜백 함수는 비동기 처리가 실패했을 때 호출된다.
new Promise((_, reject) => reject(new Error('ThisIsError')))
.then(v => console.log(v), e => console.error(e));
//Error:ThisIsError
코드 작성 시 자체적으로 에러 클래스를 생성할 경우가 있다. throw의 인수에서는 제약이 없기 때문에 Error를 상속할 필요가 없지만 호환성을 위해 Error 생성자 함수를 상속받는다.
class CustomError extends Error {
constructor(message) {
super(message);
this.name = "CustomError";
}
}
try {
throw new CustomError('에러 발생');
} catch (err) {
console.error(err); // CustomError: 에러 발생
}