새소식

SW 개발

[JS] 자바스크립트 예외처리

  • -

 

 

에러(Error)와 예외(Exception)의 차이

프로그램을 실행할 때 오작동이나 비정상적으로 종료되는 원인을 오류 또는 에러라고 합니다.

이 오류에 에러(Error)와 오류(Exception) 두 가지 종류가 있는데, '에러'는 메모리 부족, 스택오버플로우 등 발생하게 되면 복구하기 쉽지 않은 심각한 오류를 말하고, '예외'는 발생하더라도 수습할 수 있을 정도의 심각하지 않은 오류를 말합니다. 따라서 우리는 '예외 처리'를 통해 프로그램이 동작하지 않는 상황을 막을 수 있게 되는 것입니다.

 

 


 

 

자바스크립트의 예외 처리 (Exception Handling)

 

✔ 자바스크립트 내부에서의 예외

자바스크립트 문법에 어긋났거나 문법이 맞더라도 자바스크립트 내부에 정의되지 않는 코드나 함수를 참조하는 경우 예외가 발생합니다.

정의 되지 않은 Syntax, Reference를 사용할 경우

자바스크립트가 정해 놓은 규칙을 벗어났을 때 자바스크립트 내부에서 에러를 발생시킵니다.

 


 

✔️자바스크립트 내부에서 처리하지 못한 에러

그리고, 어떤 함수를 정의할 때 그 규칙에서 어긋나는 것에 대한 처리를 해줄 수 있고, 이를 '예외 처리'라고 합니다.

function sum(a, b) {
    if (typeof a !== 'number' || typeof y !== 'number') {
        throw 'type of arguments must be number type';
    }
    console.log(a + b);
}

sum(1, '4');

2개의 숫자 인자를 받아 그 합을 출력하는 함수를 생성했다고 해봅시다. 우리가 원하는 규칙은 두 개의 인자의 타입이 number여야 하고, 그렇지 않을 경우 throw를 통해 예외를 발생시켰습니다. 이때의 에러는 자바스크립트 내부에서 발생시키는 예외가 아닌, 우리가 직접 정의해준 에러입니다. 

 


 

✔️예외 처리 하기

에러가 발생하면 앞에 붙는 'Uncaught'라는 키워드의 뜻은 '잡지 못했다' 정도로 해석할 수 있는데, 에러가 있는데 잡지 못했다. 즉, 에러 처리를 해주지 않았다는 뜻이 됩니다. 이렇게 발생한 에러에 대해 어떤 처리를 해주어야지 프로그램이 정지하지 않고 다음 코드로 넘어갈 수 있기 때문에 우리는 꼭 예외상황을 정의하고 그에 대한 처리를 해주어야 합니다.

 


 

✔️일반적인 예외 처리(try catch)

// catch 해주지 않은 부분은 실행되지 않음
function f2() {
    console.log('this is f2 start');
    throw new Error('에러'); // Error 객체 - 해당하는 콜스택 정보가 담겨있다.
    console.log('this is f2 end'); // 실행되지 않음.
}

function f1() {
    console.log('this is f1 start');
    try {
        f2();
    } catch(e) {
        console.log(e);
    }
    console.log('this is f1 end');
}

f1();

f1 함수에서 f2를 호출하는데, f2 함수에서 에러가 발생했으면 에러가 발생한 지점 이후의 콜스택에 쌓인 작업들은 실행되지 않습니다. 따라서 에러가 발생할 수 있는 부분을 try() 함수내에 넣고, try() 내부에 에러가 발생한다면, 그 에러를 어떻게 처리할 것인지 catch() 부분에 작성하여 catch 부분에서 처리를 해주고 콜스택에 쌓인 작업들이 에러가 발생해도 중단되지 않고 모두 실행이 됩니다.

 

그리고 에러를 throw를 통해 발생시킬 때, 'Error occur!'과 같이 string으로 작성하지 않고 new를 통해 Error객체를 생성하고 그 안에 인자로 메세지를 작성해 줍니다. 에러 객체로 에러를 발생시키면 해당하는 콜스택 정보가 담겨 있기 때문에 어떤 파일에 몇 번째 줄에 에러가 발생했는지 볼 수 있습니다.

 


 

 

✔️비동기 상황에서의 예외 처리

일단 비동기 처리 코드(Promise, Async/await)에서의 에러 처리는 일반적인 에러 처리와는 아주 약간 다릅니다. 비동기 작업이라고 하면은 바로 콜스택에 들어가는 것이 아니라 작업 큐에서 대기했다가 콜스택이 비게되면 콜스택으로 들어와 실행됩니다. 따라서 예외가 발생하는 시점과 try가 싸고 있는 시간이 일치하지 않게 됩니다. 따라서 try catch 구문으로 에러를 잡을 수 없습니다.

 

case 1. Promise의 .catch() 이용하기

function wait(sec) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('error!');
        }, sec * 1000);
    });
}

wait(3).catch(e => {
    console.log('1st catch ', e);
});

/* chain은 같은 객체를 리턴할 때만 가능하다.*/
wait(3).catch(e => {
    console.log('1st catch ', e);
}) // wait함수의 에러를 받음
.catch(e => {
    console.log('1st catch ', e);
}); // 위 catch 구문의 상태를 받음. 에러를 잘 받았으므로 에러가 발생하지 X


/* chain 을 하고 싶을 땐. */
wait(3).catch(e => {
    console.log('1st catch ', e);
    throw e;
})
.catch(e => {
    console.log('1st catch ', e);
});

Promise가 포함되어 있는 함수의 실행부 뒤에 .catch(e => 를 통해 에러를 잡아 주면 됩니다. 이 때 주의할 점은 .catch 구문은 원래 Promise구문의 .then() 처럼 Chaining을 할 수 없다는 점입니다. 첫번 째 catch구문 이후의 catch 구문은 원래 함수의 결과를 받는 것이 아니라 이전 catch 구문의 실행 상태를 받기 때문입니다. 체이닝을 하려면 리턴하는 객체가 같아야 합니다. 따라서 .catch() 구문을 chaining 해주고 싶다면, 해당 에러를 발생시켜 다음 catch 구문에서 잡도록 해주어야 합니다.

 

 

case 2. Promise의 .then() 이용하기

wait(3).then(() => {
    console.log('Success'); // 성공했을 경우
}, e => {
    console.log('Catch in Then ', e); // 실패했을 경우
})

then을 이용해서 성공했을 경우(resolve), 실패했을 경우(reject)로 나누어 처리를 해줄 수도 있습니다.

 

 

case 3. Async/await의 에러처리

 

Async/await은 Promise와 다른 것이 아니라 Promise를 이용하는 것이라는 것을 알고 있다면 어렵지 않습니다.

async function myAsyncFunc() {
    return 'done';
}

const result = myAsyncFunc();
console.log(result); // Promise { <resolved>: "done" }

async 함수의 리턴 값은 Promise 객체의 인스턴스 입니다. 따라서 함수에서 에러가 발생하지 않고 실행에 성공했을 경우 resolved 프로퍼티가 반환됩니다.

 

 

async function myAsyncFunc() {
    throw 'myAsyncFunc Error!';
}

function myPromiseFunc() {
    return new Promise((resolve, reject) => {
        reject('myPromiseFunc Error!');
    });
}

const result = myAsyncFunc().catch(e => { console.log(e) });
const result2 = myPromiseFunc().catch(e => { console.log(e) });

따라서 async 함수를 사용할 경우 Promise와 동일하게 에러처리를 해주면 됩니다.

 

function wait(sec) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('throw Error!');
        }, sec * 1000);
    });
}

async function myAsyncFunc() {
    console.log(new Date());
    try {
        await wait(2); // Promise를 기다리는 중...
    } catch (e) {
        console.error(e);
    }
    console.log(new Date());
}

const result = myAsyncFunc();

그리고 await을 사용했을 경우 try catch 또는 .catch를 사용하면 됩니다. await 구문을 사용하게 되면 예외가 발생되는 시점이 try가 싸고 있는 시간과 일치하기 때문입니다.

 

const result = await wait(2).catch(e => { console.error(e) });

단, await구문에서 .catch 사용시 주의사항이 있는데 위 예시에서 wait함수가 resolve할때는 catch 하지 않으므로 result 에 wait의 resolve 가 그대로 들어가게 됩니다. 따라서 wait함수가 reject시 catch 구문에서는 아무것도 리턴하지 않으므로 result는 undefined가 되버립니다. 실수하지 않으려면 가급적 try catch를 사용하는 것이 좋아보입니다.

 

async function myAsyncFunc() {
    consolejljalk.log(new Date()); // Uncaught
    const result = await wait(2).catch(e => {
        console.error(e)
    });
    console.log(new Date());
}

try { myAsyncFunc(); } catch(e) {} // ==> X
myAsyncFunc().catch(e); // ==> O

그리고 우리가 의도적으로 발생시킨 예외 말고 오타나 문법오류에 의해 발생한 에러가 있으면서도 Promise를 리턴하는 함수에 대해서는 어떻게 처리해야 할까요. try catch 구문으로는 리턴하는 Promise에 대한 에러만 잡기 때문에 .catch를 이용해주어야 합니다.

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.