본문 바로가기

Language

[JS] 에러 핸들링 (Error Handling) - callback vs Promise vs async/await

비동기 호출 하면서, Error 이벤트에 대한 핸들링이 필요 합니다. 

이번에는 callback, Promise , async / await 각각 동일한 비동기 개발 시 이벤트 핸들링 하는 예제를 비교해보겠습니다. 

 

샘플 예제

// Erro Handling  : Callback    

const delayAdd = (index, callback, errCallback) => {
    setTimeout(() => {
        if (index >10){
            errCallback(`${index}는 10보다 큽니다.`);
            return;
        }
        console.log(index);
        callback(index + 1);
    },1000);
}

delayAdd(11,
    res => console.log(res),
    err => console.error(err)
)

// Resolve, Reject and Catch  : Promise
const delayAddPromise = (index) => {            // Promise는 callback 불필요
    return new Promise((resolve,reject) => {        
        setTimeout(() => {
            if (index > 10){
                reject(`${index}는 10보다 큽니다.`);
                return;            // return을 해주지 않으면 아래의 console.log(index)가 실행됨
            }
            console.log(index);
            resolve(index + 1);
        },1000);
    });
}

delayAddPromise(12)
.then(res => console.log(res))
.catch(err => console.error(err));


// Async and Await : Promise
const wrap = async () => {
    try{
        const res = await delayAddPromise(13);
        console.log(res);
    } catch (err){
        console.error(err);
    } finally {
        console.log('Done');
    }
}
wrap();

 

실행 결과

 

콜백 방식 ( Callback )

함수 정의 할때, 정상 처리( callback ) 와 에러 처리 함수 ( errCallback )를 분리 하여 정의 하였습니다.

if ( index > 10 ) 조건을 만족하면 errCallback(``) 함수를 실행하고 종료하고, 정상이면 log 출력후 callback을 호출합니다.

delayAdd( 11, res => console.log(res), err => console.error(err) ) 첫번째 파라미터는 index 와 매핑되고, 두번째는 callback 함수에 매핑되어 정상 조건이면, callback(index +1 )을 호출합니다.

이때, 화살함수 (arrow function)를 정의하면서 파라미터가 1개이므로 괄호 ( )는 생략했습니다.

에러조건이면, errCallback 함수에 매핑되고 백틱(``) 영역은 errCallback의 파라미터로 매핑되어, err로 전달되어 실행됩니다.


프로미스 방식 ( Promise )

callback 방식이 아니므로, 함수 정의 할 때 파라미터는 index를 제외하고 불필요 합니다.

Promise 객체를 반환하는데, Promise 의 경우 2가지 객체 Status가 존재합니다. 

정상 수행이 되었으면, resolve 에러가 발생 했으면 reject 를 반환하기 때문에 Promise 생성시 resolve, reject 를 받아서 각각의 함수가 호출 되도록 합니다. 

Promise 객체 반환은 반드시 resolve 또는 reject를 반환해야 하기 때문에 return 을 통해서 함수를 종료하여 다음 문장이 실행되는 것을 방지합니다.

delayAddPromise ( ) 를 새롭게 정의하고 resolve 이면 .then( )으로 받아서 처리하고 reject 이면 .catch로 받아서 에러 이벤트를 핸들링 합니다.


async / await 방식 

await을 사용하기 위해서는 반드시, async로 정의된 함수 내에서 await을 사용해야합니다. 

또한, await 함수는 Promise 객체를 반환하는 함수여야 합니다. 그래서 warp ( ) 함수를 새롭게 정의하고 async 키워드를 붙여서 await을 선언할 수 있는 함수를 정의합니다. 

정상이면 try ( ) 영역에 await 으로 함수를 호출하고, 에러이면 catch ( ) 를 정의하여 에러를 처리 합니다.

마지막 finally는 try / catch 와 무관하게 항상 수행되는 케이스를 위해서 추가 정의 하였습니다.

 

 

비동시 함수 호출을 할 때 , 순서를 보장해야 하는 데 복잡할 수록 Callback 방식은 가독성이 떨어지므로 Promise를 사용하고 조금 더 직관적인 개발을 위해서 async / awiat을 패턴을 쓰는 것을 권장합니다. try / catch 블록은 promise  chain 방식 보다 훨씬 직관적인이고 복잡한 로직을 단순하게 관리하여 유지보수성을 높을 수 있습니다.

 

 

반복문의 비동기 처리

유사하게 외부 api를 호출해서, 결과를 json 으로  변환하여 결과를 console에 뿌려주는 코드를 만들어 보겠습니다.

다만, 반복문 ( for )을 활용하여 비동기 호출을 여러번 하는 샘플을 보겠습니다. 

// 반복문의 비동기 처리

const getMovies = (movieName) =>{
    return new Promise(resolve => {
        fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=${movieName}`)
        .then(res => res.json())
        .then(json => resolve(json));
    })
}

const movieTiles =['frozen','lion king','cinderella'];

const wrap = async () => {
    for ( const movie of movieTiles){
        const res = await getMovies(movie);
        console.log(res);
    }
};

wrap();

반복문을 작성할때  for each 도 있지만, for of 를 사용하여 갯수 만큼  반복하면서 async / await을 호출하는 형태로 만들길 권장합니다.