콜백 함수를 중첩해서 사용하여 들여쓰기 지옥에 빠지는 것.
콜백함수란?
어떤 함수의 파라미터로 사용되는 함수
순차적으로 함수를 실행하고 싶을 때 사용한다.
주로 이벤트 처리나 서버 통신과 같은 비동기 작업을 수행할 때 발생한다.
여러개의 비동기 작업을 순차적으로 실행하고 싶을 때, 비동기 함수를 콜백함수로 넣게 되면 콜백 지옥이 발생하게 되는 것이다.
가독성이 떨어지고, 유지보수가 어려워진다.
setTimeout(
function (num) {
var str = num;
console.log(str);
setTimeout(
function (num) {
str += ', ' + num;
console.log(str);
setTimeout(
function (num) {
str += ', ' + num;
console.log(str);
setTimeout(
function (num) {
str += ', ' + num;
console.log(str);
},
1000,
4
);
},
1000,
3
);
},
1000,
2
);
},
1000,
1
);
// 1초 - 1
// 2초 - 1, 2
// 3초 - 1, 2, 3
// 4초 - 1, 2, 3, 4
보기만 해도 Hell이다...^^;
var str = '';
const printOne = function (num) {
str = num;
console.log(str);
setTimeout(printTwo, 1000, 2);
};
const printTwo = function (num) {
str += ', ' + num;
console.log(str);
setTimeout(printThree, 1000, 3);
};
const printThree = function (num) {
str += ', ' + num;
console.log(str);
setTimeout(printFour, 1000, 4);
};
const printFour = function (num) {
str += ', ' + num;
console.log(str);
};
setTimeout(printOne, 1000, 1);
가독성은 좋지만, 한 번만 쓸 함수를 위한 메모리 공간이 낭비된다.
(근본적인 해결법이 아님)
JS에서 제공하는 비동기 작업의 동기적 표현이 필요하다.
Promise, Generator, async/await가 있다.
Promise의 콜백함수는 바로 실행된다.
콜백함수 내부에 resolve 또는 reject 함수를 호출하는 구문이 실행되기 전까지 다음 then / catch 로 넘어가지 않는다.
new Promise(function (resolve) {
setTimeout(function () {
var str = 1;
console.log(str);
resolve(str);
}, 1000);
})
.then(function (prevString) {
return new Promise(function (resolve) {
setTimeout(function () {
str = prevString + ', 2';
console.log(str);
resolve(str);
}, 1000);
});
})
.then(function (prevString) {
return new Promise(function (resolve) {
setTimeout(function () {
str = prevString + ', 3';
console.log(str);
resolve(str);
}, 1000);
});
})
.then(function (prevString) {
return new Promise(function (resolve) {
setTimeout(function () {
str = prevString + ', 4';
console.log(str);
resolve(str);
}, 1000);
});
});
반복되는 코드를 함수화하면 깔끔한 코드가 작성된다.
const addNumber = function (num) {
return function (prevString) {
return new Promise(function (resolve) {
setTimeout(function () {
str = prevString ? `${prevString}, ${num}` : num;
console.log(str);
resolve(str);
}, 1000);
});
};
};
addNumber(1)()
.then(addNumber(2))
.then(addNumber(3))
.then(addNumber(4));
제너레이터 함수임을 나타내기 위해서 function 뒤에 *를 붙인다.
yield에서 멈추고 next를 호출하면 다시 실행된다.
const addNumber = function (prevString, num) {
setTimeout(function () {
numberGenerator.next(prevString ? `${prevString}, ${num}` : num);
}, 1000);
};
var numberGenerator = function* () {
const one = yield addNumber('', 1);
console.log(one);
const two = yield addNumber(one, 2);
console.log(two);
const three = yield addNumber(two, 3);
console.log(three);
const four = yield addNumber(three, 4);
console.log(four);
};
var numberGenerator = numberGenerator();
numberGenerator.next();
async 키워드를 붙인 함수에서
await 키워드를 만난 메서드는 동기적으로 작동한다. (메서드가 끝날 때까지 대기)
const addNumber = function (num) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(num);
}, 1000);
});
};
var numberGenerator = async function () {
var str = '';
var _addNumber = async function (name) {
str += (str ? ', ' : '') + (await addNumber(name));
};
await _addNumber(1);
console.log(str);
await _addNumber(2);
console.log(str);
await _addNumber(3);
console.log(str);
await _addNumber(4);
console.log(str);
};
numberGenerator();