JavaScript 비동기 처리 시리즈
- [JavaScript] JavaScript 기본 문법 (9) - Javascript의 비동기 처리 (2) - Promise
- [JavaScript] JavaScript 기본 문법 (10) - Javascript의 비동기 처리 (3) - async/await
동기 (Synchronous) vs 비동기 (Asynchronous)
동기 (Synchronous)
동기 방식은, 간단히 말해서 먼저 진행하던 작업을 끝내고 그 다음 작업을 시작하는 방식이다.
그러니까,
console.log("1");
// 1초 이후에 console.log("2")를 실행하는 코드.
setTimeout(function () {
console.log("2");
}, 1000);
console.log("3");
이런 코드에서 1이 출력된 이후 1초를 기다렸다가 2, 3이 출력되는 방식이라고 생각하면 된다.
결과적으로 위에서부터 순서대로 하나하나 실행하는 느낌.
비동기 (Asynchronous)
그럼 비동기 방식은 뭐냐,
반대로 먼저 진행하던 작업이 있든 말든 다음 작업을 시작해버리는 방식이다.
console.log("1");
// 1초 이후에 console.log("2")를 실행하는 코드.
setTimeout(function () {
console.log("2");
}, 1000);
console.log("3");
// output :
// 1
// 3
// 2 (1초 후 출력)
위와 똑같은 이런 코드에서, 1이 출력된 이후에 setTimeout
을 기다리지 않고 console.log("3")
을 바로 실행해버리는 것이다. 그렇게 1, 3이 출력된 이후 2는 1초가 지나고 나서야 출력이 된다.
JavaScript에서는 이렇게 비동기 방식으로 작업을 처리한다.
근데 한자로 보면 同期, 非同期 라서 同期니까 같은 시점에 시행하나? 그럼 同期방식이 다음 작업을 바로바로 시작하는 건가? 라고 생각했는데 반대더라..ㅋㅋ
그래서 비동기 (Asynchronous) 방식의 문제가 뭔데?
위에서 봤던 코드같이, 1 2 3 순서로 출력시키고 싶을 때도 있겠지만, 진짜 문제는 서버 등과 통신해서 데이터를 주고받을 때 일어난다.
function getData() {
// fetching Data.
}
const data_1 = getData();
console.log(data_1);
// output : undefined (maybe..)
getData()
함수로 서버에서 데이터를 받아와, data_1
에 저장해 출력하는 코드라고 생각해 보자.
line 5에서 getData()
로 데이터를 받아오는 데에 어느 정도 시간이 소요되고 나서, 불러온 데이터를 data_1
에 저장하게 될 것인데, line 6의 console.log(data_1)
은 그 시간을 기다려 주지 않는다.
line 5의 작업이 끝나든 말든 일단 line 6의 작업을 시작하기 때문에, 값이 할당되지 않은 data_1
을 출력해 undefined가 출력될 것이다.
이렇게, 외부와 통신하여 데이터를 가져와 그 데이터로 뭔가를 하려면, 비동기 방식으로는 해결할 수 없는 문제가 꽤나 많다.
그래서 필요한 것이 비동기(async-)식으로 동작하는 JavaScript를 동기(sync-)적으로 동작하도록 바꾸는 비동기 처리.
JavaScript에서는 비동기 처리 방식이 3가지가 있는데,
콜백(Callback)과 Promise, 그리고 async/await이다.
정확히 말하면 async/await은 Promise를 활용하는 방법 중 하나.
콜백(Callback)
Callback Usage(콜백 사용법)
function printNum(callback) {
setTimeout(() => {
callback("2");
}, 1000);
}
printNum(console.log);
// output : 2 (1초 후 출력)
- 함수의 인자에
callback
을 넣어줘요 - 함수 내에서
callback("value")
로callback
함수에 전달해줄 인수를 명시해요 - 함수 호출 시에 인자로
callback
이후 호출될 함수를 넣어줘요.
!주의할 점! line 7 에서 볼 수 있듯, 함수를 호출해서 return값을 인자로 넣어주는게 아니라, 함수 그 자체(Function의 Object)를 인자로 전달해야 되니까,
printNum(console.log())
처럼 사용하면 안됨!!
이러한 콜백을 이용해서 아까처럼 1 2 3을 순서대로 출력해보면~
function printNum_23() {
console.log("2");
console.log("3");
}
function printNum(callback) {
console.log("1");
setTimeout(() => {
callback();
}, 1000);
}
printNum(printNum_23);
// output :
// 1
// (1초 지난 후) 2
// 3
진행 순서는
- line 13에서
printNum()
호출 (callback 함수 인자로printNum_23
함수 전달.) - line 7의
console.log("1")
실행 - line 8의
setTimeout
실행. 1초(1000ms) 기다림 - 1초가 지난 후 line 9의
callback()
실행. callback()
으로 인해 콜백 인자로 받은printNum_23()
이 호출됨- line 2, 3의
console.log("2")
console.log("3")
실행
순서를 차근차근 따라가며 보면 그리 어렵지 않다.
Callback in Callback(콜백 속 콜백), 그리고 Callback Hell(콜백 지옥)
그럼 1 출력 - (1초 대기) - 2 출력 - (1초 대기) - 3 출력 이런 식으로 출력하려면 또 어떻게 해야 될까?
콜백 함수 속에 콜백을 넣고 또 콜백 함수 속에 콜백을 넣고 그 지랄을 해야 된다.
function printNum(value, callback) {
setTimeout(() => {
callback(value);
}, 1000);
}
console.log(0);
printNum(1, (value1) => {
console.log(value1);
printNum(2, (value2) => {
console.log(value2);
printNum(3, (value3) => {
console.log(value3);
});
});
});
// output :
// 0
// (1초 뒤) 1
// (1초 뒤) 2
// (1초 뒤) 3
- 0 출력
- printNum(1, (value1) => {}) 실행.
- printNum에 value로 1을 넘겨주고, setTimeout으로 1초 대기
- 1초 후 callback()으로 콜백 함수 인자였던
(value1) => {}
의 인자 'value1'에 1을 다시 넘겨줌.- console.log(value1)로 1 출력.
- 그 다음 printNum(2, (value2) => {}) 실행
- 똑같이 반복~~~
이런 방식으로 해결할 수 있다.
그런데 이렇게 3~4개 정도만 중첩되어도, 가독성이 좋지 않아지고 그에 따라 실수하게 될 가능성도 높아지며, 나중에 로직을 변경하기도 골치가 아프다.
극단적인 예시를 한 번 보자면,
function printNum(value, callback) {
setTimeout(() => {
callback(value);
}, 1000);
}
console.log(0);
printNum(1, (value1) => {
console.log(value1);
printNum(2, (value2) => {
console.log(value2);
printNum(3, (value3) => {
console.log(value3);
printNum(4, (value4) => {
console.log(value4);
printNum(5, (value5) => {
console.log(value5);
printNum(6, (value6) => {
console.log(value6);
...
...
...
});
});
});
});
});
});
이렇게 콜백이 쌓일수록 점점 오른쪽으로 높아지는 피라미드 형태가 나타나는데.. 이를 콜백 지옥(Callback Hell), 죽음의 피라미드(Pyramid of Doom) 등으로 부른다.
이러한 문제를 해결하기 위한 방법이 몇 가지가 있는데, 첫째로는 코딩 패턴으로만 해결하는 방법이다.
function print_1(callback) {
console.log(1);
setTimeout(() => {
callback(print_3);
}, 1000);
}
function print_2(callback) {
console.log(2);
setTimeout(() => {
callback();
}, 1000);
}
function print_3() {
console.log(3);
}
print_1(print_2);
이런 식으로 각각의 콜백 함수를 분리해서 콜백 지옥을 개선해줄 수 있다.
그러나 코드도 길어질 뿐더러, 따라가며 읽기도 복잡해진다.
그래서 사용하는 것이,
Promise 와
async/await 이다.
=> [JavaScript] JavaScript 기본 문법 (9) - Javascript의 비동기 처리 (2) - Promise