자바스크립트는 싱글 스레드 기반 언어입니다. 한 번에 하나의 작업을 수행한다는 것인데요, 동시에 여러 작업을 할 수 없다는 뜻이기도 합니다.
const var1 = 1;
const var2 = 2;
console.log(var1);
console.log(var2);
를 하면 1과 2가 순서대로 출력될 것입니다. 이렇게 순차적으로 동작하는 것이 동기적으로 동작한다고 볼 수 있습니다.
하지만 동기적으로만 동작한다면 한계가 있습니다. 웹 페이지의 여러 요소들이 동기적으로 동작한다면 사용자에게 정말 불편한 서비스가 될 것입니다. 첫 번째 일이 끝나지 않으면 다음 일은 수행할 수 없기 때문에, 서비스 흐름이 원활하지 않게 되는 것입니다.
자바스크립트에선 동시에 일을 처리하기 위해 비동기라는 개념을 도입했습니다. 동기와 다르게 첫 번째 작업이 끝나는 것을 기다리지 않고, 두 번째 작업을 실행하는 것이죠.
싱글 스레드 기반인 자바스크립트에서 어떻게 비동기를 구현할 수 있었을까요? 비동기를 이해하기 위해서는 javascript 내부에서 함수의 호출과 처리가 어떤 식으로 동작하는지 알아야 합니다.
Call Stack은 함수 호출을 스택으로 처리합니다.
가장 먼저 들어온 순서대로 스택에 담습니다. 그리고 가장 마지막에 들어온 것부터 처리하는 겁니다.
물론 자바스크립트는 싱글 스레드 기반이므로 하나의 콜스택을 가집니다. 한 번에 하나의 일을 처리하는 것은 변함이 없습니다.
function 
() {
console.log("Entering firstFunction");
secondFunction();
console.log("Exiting firstFunction");
}
function secondFunction() {
console.log("Entering secondFunction");
thirdFunction();
console.log("Exiting secondFunction");
}
function thirdFunction() {
console.log("Entering thirdFunction");
console.log("Exiting thirdFunction");
}
firstFunction();
이 코드의 출력결과는 어떻게 될까요?
함수 호출이 중첩될 때 콜스택에 함수가 추가되고, 실행이 끝날 때 제거될 것입니다.
// 출력 결과
Entering firstFunction
Entering secondFunction
Entering thirdFunction
Exiting thirdFunction
Exiting secondFunction
Exiting firstFunction
이 콜스택으로는 아직 작업들을 동시에 처리할 수 없습니다. 바로 Web API가 여러 작업을 동시에 처리하는 역할을 합니다.
Web API는 웹 브라우저에서 제공하는 API로, Timeout과 같은 비동기 작업을 실행합니다. 오래 걸릴 것 같은 요청을 Call Stack에서 여기로 이동시키고 나중에 처리합니다.
Callback Queue는 Web API에서 처리된 것을 넘겨받은 Callback 함수를 저장합니다.
Event Loop는 Call Stack 이 비었을 때, Callback Queue에 저장된 Callback 함수를 Call Stack으로 이동시킵니다.
개념만 들었을 때 이해하기 어려우실 수 있습니다. 예제 코드로 런타임의 동작을 알아보겠습니다.
console.log("Start"); // 1
setTimeout(() => {
console.log("Timeout Callback"); // 5
}, 0);
Promise.resolve()
.then(() => {
console.log("Promise Callback"); // 3
});
console.log("End"); // 2
실행 순서와 런타임 동작
console.log("Start")
실행console.log
가 추가되고 실행됩니다.setTimeout
실행setTimeout
의 콜백 함수는 Web API로 전달되고, 0ms 후 Macrotask Queue에 등록됩니다.Promise.resolve().then()
실행console.log("End")
실행Promise Callback
이 실행됩니다.Timeout Callback
이 실핼됩니다,.최종 출력 결과
Start
End
Promise Callback
Timeout Callback
런타임 동작에 대해 다시 정리해보겠습니다.
setTimout
, fetch
같은 비동기 작업 실행 환경을 제공한다.Promise.then
, MutationObserver
같은 비동기 작업을 처리한다.setTimout
, setInterval
, I/O
콜백 등이 등록되고 관리한다.자바스크립트 런타임 동작이 이렇게 복잡한 구조를 가지게 된 이유는 모든 일을 동시에 처리할 때 무조건적으로 발생하는 문제가 있기 때문입니다. 그것은 바로 동시성인데요. 작업 순서가 바뀌고, 데이터가 덮어쓰이거나 손실되는 일이 발생할 수 있습니다.
자바스크립트는 이 동시성 문제를 간단히 처리하기 위해 콜백 함수 방식을 사용하게 되었습니다.
브라우저 HTML 마크업 내에서도 비동기적으로 동작합니다. script
태그를 head
태그 안에 넣으면, 자바스크립트 파일이 다운로드되고 실행될 때까지 HTML 파싱이 중단됩니다. 사용자가 웹 페이지를 바로 볼 수 없게 됩니다.
해당 속성들은 자바스크립트 파일을 비동기적으로 다운로드하고 실행하게 합니다. HTML 파싱과 자바스크립트 다운로드가 동시에 진행되어 페이지 로딩 속도가 빨라집니다.
<!DOCTYPE html>
<html>
<head>
<script async src="script1.js"></script>
</head>
</html>Copy
이번 포스트는 방학동안 하고 있는 블로그 스터디의 일종으로 작성하게 되었어요. 각자 블로그를 써 온 다음, 만나서 발표를 하고 유튜브 채널에도 올리고 있습니다. 덕분에 블로그를 더 열심히 쓰게 되네요. 유튜브 채널 링크는 아래를 봐 주시면 감사하겠습니다.
Javascript 비동기 함수의 동작원리 (feat. EventLoop)
자바스크립트의 핵심 '비동기' 완벽 이해