Synchronous Processing & Asynchronous Processing
Introduction
예전에 비동기처리를 처음 배울때 너무 이해가안됐다.
단순하게 이 함수 실행하고 완료되면 다음 함수 실행되게 하고싶은 내맘을 몰라주는 js였었음.
구글링을 해도 큐 스택이니 뭐니 저러니 자라니 고라니...
지금이야 밥먹듯이 휘갈겨대는 코드지만 나같은 코찔이들을 위해 동기 비동기처리를 이해할 수 있게 최대한 쉽게 포스팅을 해보겠음.
환경은 nodejs임
# 1
setTimeout함수는 js에서 기본적으로 제공해주는 전역함수다.
console.log(global)
이걸 쳐보면(브라우저 js에서는 window치샘)
전역함수로 저렇게있다. api요청할떄 기본으로 들어있는 fetch도 저기 있어서 기본적으로 js에서 호출할수 있는거임
그럼 이 함수는 어떻게 쓸까?
setTimeout(여기에 함수가 들어감!, 여기에 시간이 들어감!)
간단하게 두번쨰 파라미터로 받는 시간(1ms단위임)이후에 첫번쨰 파라미터로 받는 함수를 호출해주는 함수다.
setTimeout(() => {
console.log("무찌야!");
}, 1000);
이 코드를 실행하면, 1초후에 console.log('무찌야!')라는 코드가 실행된다.
단순하게 그냥 저렇게 양식으로 알고있지말고, 구조를 잘 보새여.
첫번째 파라미터에는 코드가 아니라 함수리터럴이 들어간다.
떄문에 저 함수를 콜백함수로 변수참조로도 쓸수 있음.
const callMuzzi = () => {
console.log("무찌야!");
};
setTimeout(callMuzzi, 1000);
그냥 단순하게 화살표함수가 저렇게 생겼다 생각하지말고 함수리터럴이 들어간다는 개념을 알고있으면 좋다.
callMuzzi라는 변수에 함수리터럴을 할당해줬고, setTimeout에서 첫번쨰 인수로 받는건 callMuzzi라는 식별자고 여기에 함수리터럴이 담겨있는거임. 즉 함수 호출은 setTimeout내에서 직접 해준다는 소리임.
함수 리터럴에 대해서
# 먼저 리터럴에 대해서 다시 살펴보자. https://jacobowl.tistory.com/137 값(value), 리터럴(literal), 표현식(expression), 문(statement)# 리터럴(literal) 리터럴은 사람이 알아보는 기호로 적은 표기법을 말한다. 이
jacobowl.tistory.com
에전에 함수 리터럴에 대해서 쓴 포스팅
자 저렇게 구조를 잡고 보면
첫번쨰 인수로 넣은 함수가 두번쨰 인수로 넣은 시간 후에 실행이 된다는거다.
# 그럼 저 setTimeout함수의 실행순서는?
이게 솔찍히 뭔개소린가 싶을거다. 당연히 아는거 아닌가 하는걸 왜 굳이 그림까지 그렸을까 싶을텐데,
중요한점은 setTimeout함수는 이미 먼저 호출이 됐다는 것이다. 그이후 setTimeout함수에서 1초후에 callMuzzi함수를 호출해준거임.
전적으로 무슨 차이인지 명확히 알기위해서 두가지 함수로 예시를 보여주겠다.
# 처음에 내가 원했던 호출 순서 & setTimeout함수 두개 호출하기
const callMuzzi = () => {
console.log("무찌야!");
};
const callDori = () => {
console.log("도리야!");
};
setTimeout(callMuzzi, 1000);
setTimeout(callDori, 2000);
두 muzzi를 부르는 함수와 dori를 부르는 함수 두개를 만들었다.
이걸 각각 setTimeout의 첫번쨰 인자로 넣어줬고, 무찌부르는 함수는 1초후, 도리 부르는 함수는 2초후에 호출되도록 했다.
1초후에 무찌를 부르고 그 이후 2초후에 도리를 부르고 싶다
코드를 실행하면 1초후에 무찌야!가 콘솔로 찍히고 2초후에 도리야!가 콘솔로 찍힌다.
읭? 총 3초가 걸려야하는 코드가 왜 2초안에 끝났지?
# 실제 동작순서
저기서 중요한점은 setTimeout함수의 호출시점은 같다라는 점이다.
두 호출시점이 같기떄문에 두번쨰 인수로 넣어준 시간은 호출 시점으로 부터 카운팅된다.
# 그럼 어떻게 해줘야 내가 원했던 순서대로 동기처리를 할 수 있을까?
const callMuzzi = () => {
console.log("무찌야!");
};
const callDori = () => {
console.log("도리야!");
};
setTimeout(callMuzzi, 1000);
setTimeout(callDori, 2000);
아까 썼던 코드인데 이 코드의 순서를 살짝 바꿔보겠다.
const callMuzzi = () => {
console.log("무찌야!");
setTimeout(callDori, 2000);
};
const callDori = () => {
console.log("도리야!");
};
setTimeout(callMuzzi, 1000);
callMuzzi함수에서 자식함수로 callDori를 2초후에 호출하는 setTimeout함수를 넣어줬다.
이렇게하면
내가 정확히 원했던 순서대로 동작을 하게된다.
때문에 동기처리를 할 수 있게되는거임.
+ callMuzzi함수 정의시점에 자식함수인 setTimeout의 콜백인 callDori가 없는데 어떻게 참조했을까?
이건 js가 맨처음 변수선언 함수정의부분을 끌어와서 모두 시작단계에 정의해놓기때문임. 떄문에 참조할수 있는거임. 또한 부모함수의 호출시점에서 자식함수를 찾아댕기기떄문에 문제가 없었던거임.
# 위의 예시들은 setTimeout함수로 시간초를 알 수 있었다. 언제 끝날지 모르는 setTimeout 함수를 만들어보자.
const random = () => {
return Math.floor(Math.random() * 2001);
};
2000이하의 랜덤한 정수를 출력해주는 함수다.
setTimeout(callMuzzi, random());
setTimeout의 두번쨰 인수로 저 함수를 넣어주면 0초부터 2초사이의 랜덤한 시간 후에 callMuzzi()함수가 호출이 된다.
setTimeout(callMuzzi, random());
setTimeout(callDori, random());
출력을보면 저렇게 무찌가 먼저나올수도, 도리가 먼저 나올수도 있음
# 언제끝날지 모르는 세 함수로 동기처리를 해보자! 조상님들의 동기 비동기 처리 - callback함수
const callMuzzi = () => {
console.log("무찌야!");
};
const callDori = () => {
console.log("도리야!");
};
const callSeokgu =()=>{
console.log('석구야 ㅠㅠ')
}
이렇게 세 함수가 있다.
내가 원하는 처리는 이러하다.
처음 callmuzzi를 setTimeout으로 호출하고, callMuzzi가 호출되면 callDori함수를 호출하는 setTimeout을 호출한다
그리고 랜덤 초가 지나면 callDori함수가 호출되고 callSeokgu함수를 호출하는 setTimeout함수가 호출된다.
그리고 랜덤초가 지나 callSeokgu함수가 호출된다.
const random = () => Math.floor(Math.random() * 2001);
const callNextStep = (callback) => {
if (!callback) {
return;
}
setTimeout(() => {
callback();
}, random());
};
const callMuzzi = (callback) => {
console.log("무찌야!");
callNextStep(callback);
};
const callDori = (callback) => {
console.log("도리야!");
callNextStep(callback);
};
const callSeokgu = (callback) => {
console.log("석구야 ㅠㅠ");
callNextStep(callback);
};
const callAllDogs = () => {
callNextStep(() => {
callMuzzi(() => {
callDori(() => {
callSeokgu();
});
});
});
};
callAllDogs();
먼저 새로 정의해준 함수인 callNextStep함수는 콜백함수를 매개변수로 받고, setTimeout함수에 callback함수를 random초 후에 호출해주는 함수이다.
그리고 모든 함수들을 순서대로 호출해주는 callAllDogs라는 함수에 이 순서를 볼 수 있다.
코드 플로우는 자세한 설명을 생략하겠음. 읽어보면되니까.
호출해보면
모든 강아지가 순서대로 잘 불려왔다.
이게 조상님들이 사용하신 콜백함수로 동기 & 비동기처리하는 방식인데, 많이들 들어본 callback hell이 저 callAllDogs함수에서 부메랑처럼 뽈록 들어간 부분이다.
사실 난 잘 안쓰고 보통 async await이랑 promise사용함..
# 세가지 함수의 동기 비동기가 섞인 코드순서
내가 원하는 동작방식은, 언제끝날지 모르는 setTimeout함수 두 개를 같이 호출하고 이 두 함수가 모두 정확히 끝났을경우 callSeokgu함수를 호출하고싶다. 즉 처음 callMuzzi와 callDori는 비동기적으로 같은시간대에 처리되고, callSeokgu는 두 함수가 모두 완벽히 호출되면 호출하고싶음. (정확히는 js는 싱글스레드라 정확한 비동기는 아니지만 js의 동기비동기처리를 알고싶다면..)
이걸 어떻게 처리하면 좋을까?
# 이걸 어떻게 하지?
이전에 두 함수로 동기처리를 했을때는 먼저 끝날 함수를 알고있었고 이 함수안에서 setTimeout함수를 호출하고 콜백으로 dori를 부르는 함수를 호출해줬다. 근데 이번엔 뭐가 먼저 끝날지 모른다.
const random = () => {
return Math.floor(Math.random() * 2001);
};
let isMuzziFinished = null;
let isDoriFinished = null;
const callMuzzi = () => {
console.log("무찌야!");
isMuzziFinished = true;
if (isMuzziFinished && isDoriFinished) {
callSeokgu();
}
};
const callDori = () => {
console.log("도리야!");
isDoriFinished = true;
if (isMuzziFinished && isDoriFinished) {
callSeokgu();
}
};
const callSeokgu = () => {
console.log("석구야 ㅠㅠ");
};
setTimeout(callMuzzi, random());
setTimeout(callDori, random());
그래서 난 변수를 두개 정해줬다. isMuzziFinished랑 isDoriFinished라는 변수 두개를 let으로 선언해주고 첫 값은 null로 할당해줬음. 그리고 callMuzzi와 callDori함수가 호출되면 이 변수를 true값으로 재할당해주게 해줬다.
그리고 muzzi함수와 dori함수 모두 이 두 변수 둘 다 true인경우에만 seokgu함수를 호출하게 해줬다.
그림으로 보자면 이렇게 된다.
출력도 우리가 원하는 동작대로 잘 된다.
const random = () => Math.floor(Math.random() * 2001);
const callMuzzi = (callback) => {
setTimeout(() => {
console.log("무찌야!");
callback();
}, random());
};
const callDori = (callback) => {
setTimeout(() => {
console.log("도리야!");
}, random());
};
const callSeokgu = () => {
console.log("석구야 ㅠㅠ");
};
const executeCalls = () => {
let finishedTasks = 0;
const checkIfBothFinished = () => {
finishedTasks++;
if (finishedTasks === 2) {
callSeokgu();
}
};
callMuzzi(checkIfBothFinished);
callDori(checkIfBothFinished);
};
executeCalls();
gpt한테 콜백으로 짜달랬더니 븅신같이 짜줘서 내가 좀 수정함.
gpt는 전역변수로 카운트 넣는걸 싫어해서 두 함수 감싸는 부모함수인 execute함수를 하나 더 만들어줬음.
Conclusion
약 1년전에 자바스크립트 동기 비동기처리를 공부하다가 머리터지는줄 알았는데.. 지금와서 보면 너무 단순한 코드다.. 실행컨텍스트 개념이랑 호출시점 등 이런걸 알면 좀더 수월했을텐데..
당시에 구글링과 유튜브를 봐도 이해가 잘안되서 허우적댔었음.. 큐 스택 이지랄좀 그만하지. 왜 변수선언참조개념도 정확히 모르는 코찔이한테 뒤지라고 이런것만 알려주는지 모르겠음. 그들이 잘못알려준건가 아니면 내가 부족해서 이해를 못한걸까 싶다.
난 이 포스팅에 동시에 함수가 호출된다고 했지만 큐 스택개념을 알면 js의 비동기처리와 이게 왜 싱글스레드인지 알수있다. 이 분야는 내가 당시에 고민했던 부분에 비해서 너무 딥했던 부분이었음.
사실 동기비동기처리는 난 promise나 async await을 더 자주 사용한다. 근데 처음에는 날것부터 접근해봐야 이해가 더 잘될거라 믿어서 포스팅을 날것부터 써봤음. 부디 내 포스팅을 본 사람들은 이해를 쉽게 할 수 있길.