JavaScript를 배우면서 처음 만난 난관은 비동기였다. 비동기 함수를 사용하다보면 내가 원하던 실행 순서와 다르게 작동할때가 많다. 이번 글에서는 JavaScript가 어떻게 비동기적으로 작동할 수 있는지 적어보려고 한다.
1. JS는 싱글스레드이다.
자바스크립트는 콜 스택(=실행 컨텍스트 스택)이라는 함수 실행 창구를 가진다. 함수가 호출되면 콜스택에 함수가 올라가고, 실행이 종료되면 콜스택에서 빠져나간다. 자바스크립트가 싱글스레드라는 말은 콜스택이 하나이기 때문에 동시에 2개 이상의 함수를 실행할 수 없다는 의미이다.
function A() {
function B(){
}
}
A();
위의 코드는 아래 그림처럼 실행된다.

[그림 1] 싱글스레드인 JS의 콜스택
1) '전역 실행 컨텍스트'가 콜스택에 먼저 올라간다. '전역 실행 컨텍스트' 어떤 코드든 실행하면 먼저 콜스택에 올라간다. (전역 push)
2) 함수A가 호출되면서 콜스택에 A가 올라간다. (A push)
3) 함수A 내부의 B함수가 호출되고 콜스택에 B가 올라간다. (B push)
4) 함수B의 호출이 종료되면 콜스택에서 B가 빠져나간다. (B pop)
5) 함수A의 호출이 종료되고 콜스택에서 A가 빠져나간다. (A pop)
6) 모든 코드가 종료됐다. 콜스택에 제일 처음 들어간 전역 실행 컨텍스트도 종료되고 콜스택에서 빠져나간다. (전역 pop)
자바스크립트는 콜스택 하나로 진행된다. b함수의 호출이 종료되어야 a함수의 호출이 종료되는 이유도 JS가 단일스레드이기 때문이다.
2. Blocking - 싱글스레드의 문제
단일스레드는 코드의 순서를 보장해주는 장점이 있다. 하지만 코드 실행이 오래걸린다면 이후 코드의 실행이 계속 지연되는 Blocking이 발생할 수도 있다.
const get = url =>{
const xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.send();
xhr.onload = () =>{
console.log(2);
}
}
get("요청하는 url");
console.log(1);
IF ONLY 싱글스레드
만약 이 코드가 JS의 싱글스레드에만 의존한다면, get() => console.log(2) => console.log(1) 순서로 실행이 된다.
get함수처럼 서버에 리소스를 요청하는 작업은 시간이 꽤오래걸리기 때문에 콘솔에 1을 볼때까지 시간이 오래걸릴 것이다. 만약 console.log(1) 대신 화면 렌더링과 관련된 함수가 있었다면, 사용자가 이탈할지도 모른다.
SO 비동기
하지만 실제 코드를 실행하면 get() => console.log(1) => console.log(2)의 순서로 실행이 된다.
console.log(2)의 실행과 관련없이 get 호출을 종료하기 때문이다. 이렇게 특정 코드 종료를 기다리지 않고, 다음 코드를 먼저 실행할 때 '비동기'라 부른다.비동기를 사용하면 실행시간이 오래걸리는 작업이 종료되지 않아도, 뒤의 코드를 실행할 수 있기 때문에사용자 경험을 증가 시킬 수 있다.
3. 브라우저의 힘을 빌린 비동기
싱글스레드인 JS가 비동기로 작동한다는 것은 불가능해보인다.
싱글스레드: 한번에 한개의 함수만 처리한다. = 즉 1개의 함수가 끝나야 다음 함수를 실행한다.
비동기: 함수 종료와 상관없이 다음 함수를 실행한다.
사실 JS엔진은 싱글스레드이기 때문에 혼자서는 비동기가 불가능하다. 비동기가 가능한 이유는 브라우저가 태스크 큐와 이벤트 루프를 제공하기 때문이다.
태스크 큐 : 콜백함수나 이벤트 핸들러를 잠깐 대기시켜놓는 공간. 대기하고 있는 콜백함수는 콜스택이 완전히 비었을 때 콜스택으로 이동하여 실행된다.
이벤트 루프 : 태스크큐에 대기중인 함수가 있는지, 콜스택이 비었는지를 체크하는 역할

[그림2] 비동기 코드가 작동하는 원리
이벤트 루프와 태스크 큐로 get함수는 이렇게 작동한다.
1) 코드가 실행되고, 전역 실행 컨텍스트가 콜스택에 담긴다. => get이 호출되고, 콜스택에 담긴다. => get함수 내부의 비동기 코드인 xhr.onload를 만나서 'console.log(2)를 실행하는 함수' 가 태스크 큐로 이동한다.
(= 비동기 코드를 만나면 콜백함수나 이벤트 핸들러가 태스크 큐로 이동한다.)
2) get은 종료되고 콜스택을 빠져나간다. => console.log(1)이 콜스택에 들어간다.
3) console.log(1)이 종료되고, 콜스택에서 빠져나간다. => 전역 실행 컨택스트가 종료되고 콜스택에서 빠져나간다. => 콜스택이 완전히 비어있는 상태가 된다.
4) 콜스택이 완전히 비었기 때문에 이벤트루프가 태스크 큐에 있던 이벤트 핸들러를 콜스택에 넣는다.
6) 이제 console.log(2)가 실행되고 끝나면 콜스택을 빠져나간다.
이렇게 비동기 처리로 약속된 코드를 태스크 큐에 넣고, 마저 함수 처리를 하면서 JS의 싱글스레드 단점을 극복한다. 시간이 오래걸리는 http요청응답이나 setTimeout처럼 지연시간을 두는 경우 비동기 약속이 되어있기때문에 블로킹 없이 코드가 실행될수 있는 것이다.
이처럼 JS와 브라우저의 협력으로 우리는 안정적으로 화면을 볼 수 있게 된다.
'개발' 카테고리의 다른 글
| Spring Security + 세션 + 소셜로그인 (0) | 2025.01.08 |
|---|---|
| CORS 에러가 발생하는 과정 (0) | 2025.01.03 |
| Builder 패턴을 도입한 이유 (0) | 2024.12.23 |
| React Native WebView로 PortOne 결제 API 연동하기 (0) | 2024.12.04 |
| Git pull rebase, merge 차이 (0) | 2024.12.03 |