앞 포스팅에서 콜백 함수에 대해서 알아보았습니다.
그러면 이제 검색만 해도 많이 나오는 콜백 지옥에 대해서 알아보겠습니다.
예를 들기 위해서 아래와 같은 로직을 예제로 구현하였습니다.
1. 유저 입력
- 유저가 이름과 비번을 입력합니다.
2. 상품 정보 가져오기
- 데이터 베이스에서 상품 정보를 가져옵니다. 대량의 정보를 가져온다고 가정하고, setTimeout 메서드로 3초를 줍니다.
오래 걸리는 만큼 실행 순서를 2번에 두었습니다.
3. 입력 체크
- 유저가 입력한 값이 타당한지 판단합니다. 여기서는 아주 간단한 규칙만 부여하겠습니다.
- 이름 : 스트링이어야 한다.
- 비밀번호 : 3글자 이상이어야 한다.
4. 유저 입력 정보와 데이터베이스 정보 비교
- 유저가 입력한 입력 정보(이름과 패스워드) 와 서버(데이터베이스)에서 불러온 이름과 패스워드가 일치하는지 판정합니다.
- 이 처리는 서버를 이용하고 있기 때문에 setTimeout 으로 1초를 걸겠습니다.
5. 화면 출력
- 입력 값과 서버 값 비교에 문제가 없다면, 이름과 나이를 화면에 출력합니다.
여기까지 로직을 어떻게 구현할지 생각했는데, 생각해보니 유저 정보들은 서로 연관 되어있습니다.
따라서, 3번 > 4번 > 5번은 정상 처리가 된 경우에 실행된다고 가정하겠습니다.
그럼 이것을 콜백함수로 만들면 아래와 같습니다.
// 유저 입력 값 변수를 만들고, prompt 함수를 통해서 값을 저장합니다.
let name;
let pw;
name = prompt('이름');
pw = prompt('비번');
// 서버에서 불러온 유저 정보를 저장할 오브젝트를 만듭니다.
let userInfo = { name: '', pw: '', age: '' };
// 화면에 뿌려줄 HTML 요소 객체를 만듭니다.
let div_name = document.querySelector('.name');
let div_age = document.querySelector('.age');
let div_product = document.querySelector('.product');
// 앞서 구현하기로 했던 유저 정보 메서드는 서로 연관되어있기 때문에,
// 하나의 클래스에 묶어서 메서드를 선언하여 관리하도록 합니다.
class GetUserInfo {
// 유저 입력 값 체크 메서드
getUserInput(name, pw, onSuccess, onError) {
if (typeof name !== 'string' || 3 > pw.length) {
onError(new Error('입력 체크 에러'));
} else {
onSuccess(name, pw);
}
}
// 유저 입력 값과 서버에서 취득한 유저 정보 비교 메서드
compareUserData(name, pw, onSuccess, onError) {
setTimeout(() => {
let name_server = 'test';
let pw_server = '123';
let age_server = '14';
if (name !== name_server || pw !== pw_server) {
onError(new Error('아이디 혹은 비번 잘못 입력'));
} else {
onSuccess(name_server, age_server);
}
}, 1000);
}
// 유저 정보를 화면에 출력할 메서드
showUserData(userInfo, onSuccess, onError) {
if (!userInfo) {
onError(new Error('유저 정보가 없습니다.'));
} else {
onSuccess(userInfo);
}
}
}
// 서버로부터 상품 정보를 가져오는 메서드
function getProducts(callback) {
let products = new Array();
setTimeout(function () {
products.push({ product_name: '에어조던1', price: '10,000' });
products.push({ product_name: '에어조던2', price: '50,000' });
callback(products);
}, 3000);
}
// 위에서 만든 클래스를 new 키워드를 사용하여 객체를 만들고,
// 클래스 안에 있는 메서드를 사용할 수 있도록 준비합니다.
const uInfo = new GetUserInfo();
// =============================================================================
// 여기부터 위에 선언한 메서드를 호출하여 로직을 구현합니다.
// 상품 정보 취득 메서드 호출 (2번에 해당)
getProducts((result) => {
for (let i = 0; i < result.length; i++) {
div_product.textContent = div_product.textContent + result[i].product_name;
}
console.log(result);
});
// 입력 체크, 비교, 화면 출력 (3번, 4번 , 5번에 해당)
// 유저의 입력 체크 콜백 함수 호출
uInfo.getUserInput(
name,
pw,
(c_name, c_pw) => {
// 유저 입력 값과 서버 정보 비교 콜백 함수 호출
uInfo.compareUserData(
c_name,
c_pw,
(onSuccess1, onSuccess2) => {
userInfo.name = onSuccess1;
userInfo.age = onSuccess2;
// 유저 정보를 변수에 저장하고 화면에 출력하는 콜백 함수 호출
uInfo.showUserData(
userInfo,
(onSuccess) => {
div_name.textContent = div_name.textContent + onSuccess.name;
div_age.textContent = div_age.textContent + onSuccess.age;
},
(onError) => {
console.log(onError);
}
);
},
(onError) => {
div_name.textContent = div_name.textContent + 'invalid';
div_age.textContent = div_age.textContent + 'invalid';
console.log(onError);
}
);
},
(reject) => {
console.log(reject);
}
);
이 코드를 실행하면 아래와 같이 나타납니다.
실행하고 1초 후에 이름, 나이가 화면에 나타납니다.
그리고 3초 후에 상품 정보가 화면에 출력됩니다.
총 시간은 3초입니다. 상품 정보 취득하는 메서드를 비동기로 걸어놓았고 (3초) 다른 처리(유저 정보 처리 1초)를 수행하기 때문입니다.
일단 계획대로 잘 돌아가지만, 문제는 콜백함수 호출하는 부분에 있습니다. 일단 가독성이 너무 떨어지고,
로직을 변경할 떄도 상당히 복잡해 보입니다. 실제로 더 복잡한 처리를 저렇게 묶어 놓으면 더 힘들겠죠.
예를 들어, 상품 정보를 취득하는 메서드를 전체 상품 정보 가져오는 것이 아니라 유저가 구입한 상품 정보만
현하도록 변경이 되었다고 가정한다면 유지 보수가 생각보다 쉽지 않겠죠.
에러도 적절히 처리해줘야 하고, 영향 조사도 해야 한다고 가정하면 이래저래 일이 많아 질 것 같습니다.
이와 같은 구조가 콜백 지옥이라 불립니다. 이것을 개선하기 위해서 Promise 와 async 를 사용합니다.
제가 인터넷 강의와 여러 포스팅을 참고하면서 샘플로 만들어 보긴 했는데 적절한지는 모르겠습니다.
문제가 있다면 댓글로 언제든지 남겨주시기 바랍니다.
다음 포스팅은 위의 코드를 Promise 와 async 를 활용해서 바꾸는 작업을 해보겠습니다.
'JavaScript' 카테고리의 다른 글
JavaScript - Promise 에 대해서 (수정 중..) (0) | 2020.09.25 |
---|---|
JavaScript - 콜백 함수 (0) | 2020.09.23 |
JavaScript : 반복문 (0) | 2020.09.21 |
JavaScript : Promise 기초 (동기 처리에서 비동기 처리로 바꾸는 과정) (0) | 2020.09.19 |
JavaScript - Array (0) | 2020.09.17 |