자바스크립트 에러 처리(Error Handling) 총정리

자바스크립트에도 다른 언어들에 준하는 런타임 예외 처리 구문이 있습니다.

짧은 코드를 작성할 때는 이런 런타임 에러들이 큰 문제가 안되기도 하고, 쉽게 해결할 수 있습니다.

자바스크립트 코드가 길어지고 로직이 복잡해지면서 파일이나 네트워크 연결을 제어하는 수준이 되면 무수히 많은 에러와 예외 상황이 개발자를 괴롭히게 됩니다.

자바스크립트 에러 핸들링은 이런 예외, 또는 에러 상황을 매끄럽고 부드럽게 처리를 할 수 있도록 해줍니다.

의미를 알기 힘든 알 수 없는 기술 용어로 범벅된 에러 메시지보다는 코드를 작성한 개발자가 이해하고 구분할 수 있는 메시지가 훨씬 더 도움이 될 수 있습니다.

에러 처리에 사용하는 핸들링 키워드는 try, catch, finally, throw 4개입니다.

그밖에 에러 객체 몇 개를 더 알아야 할 수도 있지만, 기본 예외처리 구문은 키워드 4개만으로 모두 작성할 수 있습니다.


기본 에러 처리

예외처리를 하는 기본 구문은 다음과 같습니다.

try{} 구문 안에는 예외가 발생할 수 있는 코드를 작성합니다.

그리고 try() 구문은 예외가 발생하는지를 체크합니다.

try{
    // 예외를 발생시킬 수 있는 코드
}

try{} 구문만으로는 예외가 발생했을 때 할 수 있는 처리는 없습니다. try{} 구문은 예외가 발생하는지만을 체크합니다.

에러가 발생했을 때 처리하는 코드는 catch(Error){} 구문으로 작성합니다.

catch(){} 구문은 단독으로는 사용할 수 없고 반드시 try{} 구문과 함께 사용해야 합니다. if ~ else와 같다고 생각하면 됩니다.

에러가 발생하면 에러메시지를 표시하고 코드 실행이 종료되는 것이 아니라 코드 실행이 catch(){} 구문으로 넘어오고 catch(){} 구문 안의 코드가 실행됩니다.

이때 catch(){} 구문은 인자(Parameter)로 Error(Error) 객체를 함께 넘깁니다.

에러 객체에는 에러가 발생한 이유를 알려주는 여러 가지 정보가 들어있습니다.

대표적으로 Error.message 값에는 에러가 발생하면 콘솔에 표시되는 자바스크립트 에러 메시지가 들어 있습니다.

try{
    // 예외를 발생시킬 수 있는 코드
}
catch(err){ //인자로 에러(Error) 객체가 넘어옴
    // 에러가 발생하면 실행되는 코드
}

코드에 정의되지 않은 함수를 호출하면 에러가 발생하는데 try~catch(){} 구문으로 에러 핸들링을 해서 매끄럽게 코드가 처리되도록 해보겠습니다.

먼저 정의되지 않은 함수를 호출해서 에러메시지가 어떻게 발생하는지 확인해보겠습니다.

readFile();
console.log('end!');

readFile() 함수가 정의되어 있지 않기 때문에 "readFile is not defined"라는 참조 에러가 발생합니다.

그리고 코드는 에러가 발생한 위치에서 더 이상 실행되지 않고 종료됩니다. 중요합니다!

try ~ catch 구문으로 에러 핸들링을 해서 에러 없이 코드가 실행 완료되도록 해보겠습니다.

try{
    readFile();
}catch(err){
    console.log("에러원인: "+err.message);
}
console.log('end!');

정의되지 않은 함수인 readFile() 함수를 호출하면서 에러가 발생하고, 에러를 캐치한 try 구문은 실행 제어를 catch 구문으로 넘깁니다. 콘솔에는 catch(){} 구문에서 출력하려는 콘솔 메시지가 출력됩니다.

그리고 에러를 catch(){} 구문에서 정상 처리해서 종료했기 때문에 자바스크립트 코드는 정상 실행되고 마지막 행의 콘솔 출력 메시지를 출력하는 행이 정상 실행됩니다. 


임의로 에러를 발생시키기

실제로 에러가 발생하지 않아도 임의로 에러를 발생시킬 수 있습니다.

예를 들어 조건에 맞지 않는 값이 들어왔을 경우 에러를 발생시키고 에러 핸들링 처리를 하도록 해서 이후에 발생할 에러를 미리 막을 수 있습니다.

적절하게 에러를 발생시키고 에러 처리를 하는 것은 표준적인 자바스크립트 코딩 방법입니다. 특별히 예외적이거나 비 정상적인 방식이 아니며, 간결하고 유지보수가 쉬운, 그리고 코드가 더 안정적으로 실행되도록 하는 권장하는 코딩 방법입니다.

에러를 발생시키는 구문은 throw 입니다.

간단하게 다음과 같이 작성하면 에러가 발생합니다. 물론 에러 핸들링 처리를 하지 않으면 throw 에러를 발생시킨 지점에서 코드 실행은 멈춥니다.

throw "에러발생!";

콘솔에는 다음과 같이 에러가 표시됩니다.

기본 에러 발생 외에 여러가지 에러 객체를 이용해 다양한 에러를 발생시킬 수 있습니다.

throw "에러발생";
throw Error("에러발생");
throw new ReferenceError("참조 에러발생");

UserException('사용자 정의 에러 발생');


finally로 무조건 실행되는 코드 추가하기

finally 구문 또한 catch 구문처럼 try 구문에 종속된 구문입니다. 단독으로는 사용할 수 없습니다.

try 구문의 실행 코드 영역과 catch 구문의 실행 코드 영역 어느 쪽이 실행돼도, 마지막에는 무조건 실행돼야 하는 코드를 finally 구문에 추가할 수 있습니다.

예를 들어 파일에서 내용을 읽은 후 파일을 닫는 처리를 하거나 비정상 값에 대해 에러 처리를 한 후 값을 강제로 기본값으로 보정하는 처리를 finally 구문으로 할 수 있습니다.

finally 구문 구조는 다음과 같습니다. catch 구문은 없을 수 있습니다.

try{

    // 실행 코드

}finally{

    // 항상 실행

}

try{

    // 실행 코드

}catch(err){

    // 에러 처리

}finally{

    // 항상 실행

}

배열의 요소 값이 0보다 작거나 100보다 크면 0으로 값을 변경하는 코드를  finally 구문으로 에러 처리를 해보겠습니다.

음수이면 에러를 발생시켜서 에러 메시지를 콘솔에 표시되게 하고, 100보다 큰 값은 에러는 아니지만 값은 0으로 초기화를 해야 합니다.

두 가지 모두 값을 초기화해야 하므로 초기화 코드는 finally 구문에 작성을 합니다.

배열의 인덱스 idx 값은 var 변수이기 때문에 글로벌 변수가 돼서 finally 구문에서도 접근할 수 있습니다. 

let arrNum = [4,23,7,1,-5,13];

try{
    for(var idx = 0; idx < arrNum.length; idx++){
        if(arrNum[idx] < 0){
            throw Error(idx + '번째 요소 음수: '+ arrNum[idx]);
        }else if( arrNum[idx] > 100){
            break;
        }
    }
}catch(err){
    console.log(err.message);
}finally{
    //항상 실행
    if(idx < arrNum.length)
        arrNum[idx] = 0;
    console.log(arrNum);
}

프로미스 비동기 처리 에러 핸들링

자바스크립트의 특성상 비동기 처리는 뺄 수 없는 기본 중의 기본인 코딩 방식 중의 하나입니다.

그중에서 대표적인 프로미스 비동기 처리를 할 때 에러 핸들링은 일반적인 에러 핸들링과 다른 방식으로 작성해야 합니다.

비동기 처리의 실행 순서에 따른 이해가 없이 에러 처리를 하게 되면 에러가 발생해도 에러 처리가 되지 않게 됩니다.

setTimeout() 함수로 간단한 비동기 상황을 만들어서 비동기 코드 작성을 할 때는 어떻게 에러 처리를 해야 하는지 알아보겠습니다.

try {
    setTimeout(function() {
        throw Error('에러 발생');
    }, 3000);
} catch (err) {
    console.log('에러 핸들링: '+err.message);
}
console.log('코드 종료.');

이 코드는 catch 구문의 메시지가 절대 표시되지 않습니다. 

setTimeout() 안의 지연 함수가 3초 후에 실행되면서 에러를 발생시키는 코드가 실행되지만 이미 모든 코드가 실행 완료가 돼서 catch 구문으로 코드 제어가 넘어갈 수 없습니다.

콘솔 창에는 다음과 같은 에러 메시지가 표시되고 throw로 에러를 발생시킨 위치에서 코드 실행이 종료됩니다.

앞의 코드는 다음과 같이 변경을 해야 합니다.

setTimeout(function() {
    try {
        throw Error('에러 발생');
    } catch (err) {
        console.log('에러 핸들링: '+err.message);
    }
}, 3000);
console.log('코드 종료.');

에러가 발생할 수 있는 코드를 작성할 때 에러 핸들링 구문을 비동기 코드로 분리해서 사용하면 안 됩니다. 에러 핸들링은 항상 동기 코드로 실행 순서대로 실행되도록 작성해야 합니다.

비동기 코드를 작성할 때 에러 핸들링 코드를 어떻게 작성해야 하는지와 그 이유를 알았으므로 프로미스 객체를 사용할 때 비동기 코드를 어떻게 작성하는지 알아보겠습니다.

const p = new Promise((resolve, reject)=>{
    setTimeout(()=>resolve('1'),3000);
}).then((str)=>{
    try{
        if(parseInt(str) !== 0)
            throw Error('에러발생!');
    }catch(err){
        console.log(err.message);
    }
});

프로미스 객체에서 3초 지연 후 프로미스의 리졸브를 호출하면 비동기 실행 메서드인 then()에서 리졸브 인자로 받은 값을 체크해서 원하는 값이 아니면 에러 핸들링 처리를 하게 됩니다.