
console.log("시작");
function synchronousFunction() {
console.log("동기 함수 시작");
console.log("동기 함수 끝");
}
synchronousFunction();
console.log("끝"); // 시작 -> 동기 함수 시작 -> 동기 함수 끝 -> 끝
동기 방식이란 작업이 순차적으로 진행되는 방식을 말한다. 즉, 한 작업이 끝나야 다음 작업이 실행되는 것이다.
위 예시의 경우, 코드가 한 줄씩 순서대로 실행되기 때문에 현재 작업이 완료되어야 다음 작업이 시작되므로, 함수 내부의 시작과 끝이 모두 출력되고 나서야 함수 밖의 "끝" 이 출력된다.
console.log("시작");
function asynchronousFunction() {
console.log("비동기 함수 시작");
setTimeout(function() {
console.log("비동기 함수 끝");
}, 1000);
}
asynchronousFunction();
console.log("끝"); // 시작 -> 비동기 함수 시작 -> 끝 -> 비동기 함수 끝
비동기 방식은 작업이 순차적으로 진행되지 않고, 현재 작업이 완료되기를 기다리지 않은 채 다음 작업을 실행하는 방식을 의미한다. 주로 이벤트 처리, 파일 읽기나 쓰기, 네트워크 요청과 같이 시간이 오래 걸리는 작업에 사용된다.
위 예시의 경우, setTimeout 이 비동기 작업을 하기 때문에 setTimeout 의 동작이 채 끝나기 전에, 바로 다음 작업을 수행한 것을 확인할 수 있다.
// 1번
let num = 1;
// 2번
setTimeout(() => {
num = 2;
}, 0);
// 3번
num = 3;
// 4번
console.log(num); // 3
자바스크립트에서의 비동기 동작은 특이하게 이루어진다. 위 예시에서 setTimeout 의 지연시간이 0초로 설정되어 사실상 동기 작동과 다름없이 바로 작업이 이루어지는데도, 코드는 setTimeout 을 지나쳐 나머지 코드를 먼저 수행한 것을 확인할 수 있다. 사실 위의 예시를 이해하기 위해서는, 브라우저에서 자바스크립트의 비동기 작업이 어떤 식으로 이루어 지는지를 파악하고 있어야 한다.
'Single Threaded 언어' 라는 것은 언어가 동시에 하나의 작업만을 수행할 수 있다는 것을 의미한다. 여기서 thread 는 프로그램의 실행 단위로, 각 thread 는 독립적으로 코드를 실행한다.
그런데 자바스크립트는 사실 'Single Threaded 언어' 로, 개발 초기에는 자바스크립트가 웹 페이지와의 간단한 상호작용을 담당하는데에 목적을 두고 개발되었기 때문에 이처럼 설계되었다. 때문에 자바스크립트는 한번에 하나의 작업만을 처리할 수 있는 언어이다.
그런데 만약 시간이 오래 걸리는 코드를 자바스크립트에서 동기적으로 처리하게 된다면 어떻게 될까?
자바스크립트는 한번에 하나의 작업만 처리하므로, 모든 코드를 실행하는데 꽤나 많은 시간이 소요될 것이다. 따라서 자바스크립트는 이러한 작업들을 비동기적으로 처리하기 위한 시스템이 마련되어 있다. 비동기 방식을 이용하면 시간이 오래 걸리는 작업의 완료를 기다리지 않고도 동시에 여러 작업을 수행이 가능하여, 브라우저에서 웹 페이지의 반응성을 유지하면서도 다양한 작업을 수행할 수 있다.

자바스크립트에서 코드가 실행되면 위의 흐름을 거쳐서 코드가 실행되는데, Call Stack 은 직접적으로 코드의 처리를 담당하는 구간이라고 볼 수 있다. 동기적 작업에 대해서는 여기서 바로바로 처리가 이루어진다.
그런데 'setTimeout' 함수나, 이벤트리스너, fetch 같은 Ajax 함수들 처럼 '비동기 작업' 을 처리하는 코드들은 바로 Call Stack 에서 처리되지 않는다. 자바스크립트는 이러한 비동기 작업들을 먼저 WebAPIs 와 같은 백그라운드에서 수행하게끔 만들고, 수행이 끝나면 Callback Queue 에 후처리 작업들을 순차적으로 옮긴다. 그리고 Call Stack 의 모든 작업이 끝나 Stack 이 비어있게 되면 Queue 의 작업들이 Stack 에 차례대로 들어오게 되어 마침내 비동기 작업도 실행이 완료된다.
위와 같은 루프를 통해서 자바스크립트는 복잡한 웹 브라우저의 작업들을 비동기적으로 처리하기 때문에, 우리가 웹 페이지에서 다양한 경험을 하는 동안 끊기지 않고 빠르게 상호작용이 가능한 것이다.
// 1번
let num = 1;
// 2번
setTimeout(() => {
num = 2;
}, 0);
// 3번
num = 3;
// 4번
console.log(num); // 3
그럼 여기까지 알고나서 다시 위의 코드를 살펴보자.
Call Stack 에는 맨 위의 코드부터 맨 아래의 코드까지 순차적으로 들어가게 된다. 그런데 setTimeout 이라는 비동기함수가 발견되면, 자바스크립트는 바로 이를 백그라운드에서 처리하게끔 Stack 에서 빼버린다. 그 동안에 Stack 에서는 그 아래의 동기적 작업들이 순차적으로 이루어진다. 그리고 백그라운드에서 비동기 작업이 종료되면, num = 2 라는 후처리 작업이 Stack Queue 로 이동한다. 그 후에 console.log(num) 이라는 코드까지 끝나 Call Stack 이 비게 되면, 그제서야 Queue 에 있던 num = 2 가 Stack 으로 넘어와 실행되는 원리인 것이다.