728x90

javascript에 대해 말할 때면 흔히 asynchronous한 특성에 대해 많이 말하는 것 같습니다.

 

asynchronous(비동기적)하다고 하면

여러 함수가 병렬적으로(parallel) 실행이 되고, 각 함수의 실행이 완료되지 않았더라도 다른 함수도 실행시켜버리는 것입니다. 

반대로 synchronous(동기적)하다고 하면 

코드에 쓰여진 순서 그대로 하나 하나 실행이 되고, 실행하고 있는 함수가 완료되지 않으면 다음 함수로 넘어갈 수 없는 것입니다.

보통 C언어, python 등을 배우신 분들은 synchronous한 실행에 더 익숙하실 듯 합니다.

 

하지만 웹에서 자주 사용되는 javascript의 경우, 브라우저가 여러 리소스를 받아오고 화면에 뿌려줄 때,

리소스 여러 개 중 한 개를 받아오는데 실패했다고 해서 그 다음에 받아올 리소스를 받아오는 작업을 못해버리면 안 되기 때문에!

웹에서는 병렬적으로 여러개의 작업을 와랄랄라 처리해버리는 javascript가 유리한 듯 합니다.

 

물론 javascript라고 해서 항상 비동기적으로 모든 함수가 병렬적으로 실행되는건 아닙니다. 

이거 헷갈려서 "자바스크립트가 그래서 동기적이라는거야 비동기적이라는거야? " 했지만 

기본적으로는 동기적이고요, 비동기적인 함수는 비동기적으로 실행됩니다.

javascript에서는 비동기 함수를 비동기적으로 실행하면서 기본적으로 콜백함수(callback function)라는 것을 이용하여 각각의 작업이 끝난 뒤에 무언가를 실행해줄 수 있도록 합니다. 

예를 들면, 해당 비동기 함수에서 완료하여 만든 값에 대한 처리가 필요할 때 처리 부분을 콜백함수에 넣어줄 수 있습니다.

 

javascript의 대표적인 비동기 함수로 setTimeout()이 있습니다.

 

setTimeout(function, milliseconds);

'milliseconds' 만큼의 ms 이후에 'function'을 실행시키는 함수입니다.

 

그러면 이 코드는 돌렸을 때 

setTimeout(() => console.log("3초 뒤에 저를 실행시켰군요!"), 3000);
console.log("내가 먼저 나올거지롱.")

(1) 3초가 흐른 뒤

(2) "3초 뒤에 저를 실행시켰군요!" 

(3) "내가 먼저 나올거지롱."

 

요렇게 콘솔창에 뜰 것만 같은데. 실제 결과는 

 

(1) "내가 먼저 나올거지롱."

(2) 3초가 흐른 뒤

(3) "3초 뒤에 저를 실행시켰군요!" 

 

이렇게 된답니다 (!) 

 

저는 이게 당황스러웠어요.

 

위의 비동기 동기 방식을 이해하고 계신 분들은

"그야 setTimeout이 비동기 함수니까 병렬적으로 setTimeout함수와 "내가 먼저 나올거지롱." 출력 함수를 동시에 병렬적으로 실행시켰고, 출력함수가 더 빨리 끝나니까 나온거겠지."

하실 수 있습니다.

맞는 말인 듯 합니다.

 

그렇지만 이 이면에는 특이한 javascript만의 실행방식이 있다고 하네요.

javascript의 비동기 함수의 경우, 보통 함수의 실행이 끝난 후에 매개변수로 받았던 callback 함수를 호출합니다. 그렇기 때문에

이 callback함수는 해당 비동기 함수의 실행이 끝난 후에  Event Queue (FIFO, First In First Out) 에 들어가게 되고, 

비동기 함수든 동기 함수든 함수들은 모두 쓰여진 순서대로 Call Stack (LIFO, Last In First Out)에 들어가게 됩니다. 

 

저 용어가 괜히 복잡하시면, 그냥 2 개의 다른 통에, 그것도 하나는 FIFO고 하나는 LIFO인 통에 함수와, 비동기 함수의 콜백함수가 나눠져서 들어간다고 생각하시면 되어요.

 

따라서 진행되는 과정을 뜯어보면...

 

(1) 첫 번째 라인부터 읽어 setTimeout함수를 실행합니다. setTimeout함수가 '하는 일'은 '3000ms동안 가만히 있는 것' 입니다. 추가적으로 비동기함수이기 때문에 자기가 '하는 일'을 마치고 매개변수로 넘어온 callback 함수를 호출해버립니다.

setTimeout(() => console.log("3초 뒤에 저를 실행시켰군요!"), 3000);

 

(2) 첫 번째 라인을 읽었으니 이제 두 번째 라인으로 가서 

console.log("내가 먼저 나올거지롱.")

를 실행합니다. 그리고 바로 콘솔창에 "내가 먼저 나올거지롱." 가 보이게 됩니다.

 

(3) (1)번 과정 실행 3초 뒤, 즉 setTimeout의 '하는 일' 완료 후 setTimeout 함수는 callback 함수를 호출합니다. 이 때, callback 함수는 Event Queue에 들어갑니다. 

Event Queue에 들어간 함수는 결국

() => console.log("3초 뒤에 저를 실행시켰군요!")

가 됩니다.

 

(4) Event Loop (누구세요?) 라는 아이가 Call Stack이 비어있을 때 Event Queue에 있는 함수를 실행시켜 줍니다. 즉 

() => console.log("3초 뒤에 저를 실행시켰군요!")

를 실행시켜버릴 것입니다.

 

Event Loop라는 아이는,

1) Call Stack에 있는 차곡차곡 쌓여있는 함수를 LIFO로 실행해주고(사실은 함수 안의 함수가 있을 경우에 LIFO이고, 쓰여진 함수 순서대로 동기적으로 실행시킵니다),

2) Call Stack에 있는 함수를 다 실행한 경우에 Event Queue에 차곡차곡 쌓여있는 콜백함수들을 FIFO로 실행해줍니다.

 

Loop라는 이름은 Call Stack이 비어있는지 아닌지를 계속해서 루프로 확인한다는 의미인 것 같네요(아님 말구).

 

그래서 위 예제에 대한 결론은, 

console.log("내가 먼저 나올거지롱.")

함수는 아무리 setTimeout 함수보다 나중에 실행되긴 했지만서도, setTimeout에서 호출하는 callback 함수인

() => console.log("3초 뒤에 저를 실행시켰군요!")

보다는 우선순위를 가져서 (왜냐면 Event Loop는 Event Queue보다 Call Stack에 있는 것들을 먼저 실행시켜주고 싶어하기 때문에) 먼저 콘솔창에 나왔습니다. 

 

이상입니다. 

하지만...

 

다음에 더 쓸지도 몰라요.

이것보다 더 중요한 게 아직 안 나왔잖아요.

 

콩나물2

+ Recent posts