콜백지옥이란?
콜백 지옥은 비동기 처리 로직을 위해 콜백 함수를 연속해서 사용할 때 발생하는 문제이다.
콜백 함수를 이용해서 비동기 처리를 해주다가 코드가 깊어지는 것을 의미하며, 일반적으로 콜백지옥을 해결하는 방법에는 이전에 포스팅한 Promise와 Async 방법이 있다.
function taskA(a, b, cb) {
setTimeout(() => {
const response = a + b;
cb(res);
}, 2000);
}
function taskB(a, cb) {
setTimeout(() => {
const response = a * 2;
cb(response);
}, 2000);
}
function taskC(a, cb) {
setTimeout(() => {
const response = a * -1;
cb(res);
}, 2000);
}
taskA(1, 2, (res_a) => {
taskB(res_a, (res_b) => {
taskC(res_b, (res_c) => {
console.log("taskC Result : ". res_c);
});
});
});
위의 코드를 확인해보자, 다음과 같이 콜백 함수에 콜백 함수를 넣어서 비동기 처리의 결과를 또 다른 비동기 처리의 매개변수로 전달을 할 수 있다. 콜백 함수는 주로 이벤트 처리나 서버 통신과 같은 비동기 작업을 제어하기 위해 사용되지만 콜백 함수를 반복해서 사용하게 되면, 들여쓰기 수준이 감당할 수 없을 정도록 깊어진다.
Promise
프로미스란 비동기 연산이 종료된 이후, 결과를 알기 위해 사용하는 객체이다.
프로미스를 사용하면 비동기 메서드를 마치 동기 메서드처럼 값을 반환할 수 있으며, then과 catch문의 체이닝을 통해 값을 반환하며 받아올 수 있다. Promise는 then으로 함수의 실행 순서를 정할 수 있지만, callBack처럼 함수들이 많아질수록 작성하기가 어렵고, 가독성이 떨어진다는 단점이 있다.
const testPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("2초 후에 실행되는 함수가 정상적으로 실행되었어요!");
}, 2000);
});
testPromise.then((response) => {
console.log("우와!" + response); // 우와! 2초 후에 실행되는 함수가 정상적으로 실행되었어요!
});
Promise 기반 비동기적 함수를 호출하면 그 함수는 Promise 객체를 반환한다. 대기중인 Promise는 값과 함께 성공(fullfilled), 실패(Rejected)를 할 수 있다. 이행되거나 거부될 때 프로미스의 then 메서드에 의해 대기열(큐)에 추가된 처리기들이 호출된다.
Promise는 new Promise 생성자를 통해서 Promise 객체를 만들 수 있다.
Promise 생성자 함수는 비동기 처리를 수행할 콜백 함수를 인수로 전달받는데, 이 콜백함수는 resolve와 reject 함수를 인수로 전달받는다.
const successPromise = new Promise((resolve, reject) => {
resolve("성공적으로 데이터가 발생했어요!");
});
successPromise
.then((response) => {
console.log(response); // 성공적으로 데이터가 발생했어요!
})
.catch((error) => {
console.log(error);
})
const failPromise = new Promise((resolve, reject) => {
resolve("에러가 발생했어요!");
})
failPromise
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error); // 에러가 발생했어요!
})
resolve 안에 데이터를 넣게되면 성공 시에 나타낼 데이터를 반환해주고, reject 안에 데이터를 넣게되면 실패 시에 나타낼 데이터를 반환해준다. 프로미스의 비동기 처리 상태가 변화하면 이에 따른 후속처리를 해줘야 하는데, 예를 들어 프로미스가 fulfilled(성공) 상태가 되면 프로미스의 처리 결과를 통해 무언가를 해야하고, rejected(실패) 상태가 되면 프로미스의 처리 결과(Error)를 가지고 에러 처리를 해야한다.
이를 위해 Promise는 후속처리메서드인 then, catch, finally를 제공하는 것이다. 즉, 프로미스의 비동기 처리 상태가 변화하면 후속처리메서드에 인수를 전달한 콜백함수가 선택적으로 호출된다.
Async · Await
ES8에서 도입된 Async · Await를 사용하면 비동기 함수를 마치 동기적 코드인 것 처럼 동작하도록 구현할 수 있다. 즉, 프로미스의 후속처리 메서드(then, catch, finally) 없이 마치 동기 처리처럼 프로미스가 처리 결과를 반환하도록 구현할 수 있다.
비동기 함수는 항상 Promise 객체를 반환한다는 특징이 있다. Async 함수는 Async 키워드를 사용해 언제나 프로미스를 반환한다. Async 함수가 명시적으로 프로미스를 반환하지 않더라도 Async 함수는 암묵적으로 반환값을 Resolve하는 프로미스를 반환한다.
// 일반 함수에서 async 키워드를 사용하는 방법
async function test() {
// ..logic
}
// 화살표 함수에서 async 키워드를 사용하는 방법
test = async() => {
// ..logic
}
await은 async 안에서만 동작을 한다. await 키워드를 쓰게 되면 해당 값이 반환되기 전까지 기다리는 동안 async 내부 함수는 일시 중단이 된다. 프로미스가 settled (비동기 처리가 수행된 상태) 상태가 될때까지 대기하다가 settled 상태가 되면 프로미스가 resolve한 처리 결과 값을 반환한다.
async function sumOfFetch() {
try{
const user = await 함수명;
return user;
} catch (e) {
console.log(e);
}
}
Async · Await 예외 처리는 try-catch 문을 사용한다. 실행되는 부분은 try문으로 감싸고, error가 발생하는 부분은 catch로 블럭을 나누면 된다. 대체적으로 Promise 방법보다 작성하기 쉽고 가독성이 올라가는 효과를 얻을 수 있다.
(다만, 지옥을 완전한 해결하는 방법은 아니다 이를 위해서 지옥을 방지하기 위해 callBack 함수와 Promise, Asnyc · Await을 적절히 섞어 쓰는 것이 좋다)
레퍼런스
댓글