자바스크립트 비동기의 이해

서성원·2025년 1월 20일
0

자바스크립트

목록 보기
6/6
post-thumbnail

동기

자바스크립트는 싱글 스레드 기반 언어입니다. 한 번에 하나의 작업을 수행한다는 것인데요, 동시에 여러 작업을 할 수 없다는 뜻이기도 합니다.

const var1 = 1;
const var2 = 2; 

console.log(var1);
console.log(var2);

를 하면 1과 2가 순서대로 출력될 것입니다. 이렇게 순차적으로 동작하는 것이 동기적으로 동작한다고 볼 수 있습니다.

하지만 동기적으로만 동작한다면 한계가 있습니다. 웹 페이지의 여러 요소들이 동기적으로 동작한다면 사용자에게 정말 불편한 서비스가 될 것입니다. 첫 번째 일이 끝나지 않으면 다음 일은 수행할 수 없기 때문에, 서비스 흐름이 원활하지 않게 되는 것입니다.

비동기

자바스크립트에선 동시에 일을 처리하기 위해 비동기라는 개념을 도입했습니다. 동기와 다르게 첫 번째 작업이 끝나는 것을 기다리지 않고, 두 번째 작업을 실행하는 것이죠.

싱글 스레드 기반인 자바스크립트에서 어떻게 비동기를 구현할 수 있었을까요? 비동기를 이해하기 위해서는 javascript 내부에서 함수의 호출과 처리가 어떤 식으로 동작하는지 알아야 합니다.

CallStack

Call Stack은 함수 호출을 스택으로 처리합니다.
가장 먼저 들어온 순서대로 스택에 담습니다. 그리고 가장 마지막에 들어온 것부터 처리하는 겁니다.

물론 자바스크립트는 싱글 스레드 기반이므로 하나의 콜스택을 가집니다. 한 번에 하나의 일을 처리하는 것은 변함이 없습니다.

function ![](https://velog.velcdn.com/images/seongwon__105/post/213ff199-634c-4460-9f67-812d938ef98f/image.png)
() {
  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으로 이동시킵니다.

  • macrotask : setTimeout, setInterval, setImmediate, UI rendering
  • microtask : Promises, MutationObserver

개념만 들었을 때 이해하기 어려우실 수 있습니다. 예제 코드로 런타임의 동작을 알아보겠습니다.

console.log("Start"); // 1

setTimeout(() => {
  console.log("Timeout Callback"); // 5
}, 0);

Promise.resolve()
  .then(() => {
    console.log("Promise Callback"); // 3
  });

console.log("End"); // 2

실행 순서와 런타임 동작

  1. console.log("Start") 실행
  • 콜스택에 console.log가 추가되고 실행됩니다.
  • 출력: Start
  1. setTimeout 실행
  • setTimeout 의 콜백 함수는 Web API로 전달되고, 0ms 후 Macrotask Queue에 등록됩니다.
  • 콜스택: 빈 상태
  1. Promise.resolve().then() 실행
  • then의 콜백 함수는 Microtask Queue에 등록됩니다.
  • 콜스택: 빈 상태
  1. console.log("End") 실행
  • 콜스택에 추가되고 실행됩니다.
  • 출력: end
  1. Microtask Queue 실행
  • Promise Callback이 실행됩니다.
  • 출력: Promise Callback
  1. Macrotask Queue 실행
  • Timeout Callback이 실핼됩니다,.
  • 풀력: Timeout Callback

최종 출력 결과

Start
End
Promise Callback
Timeout Callback

런타임 동작에 대해 다시 정리해보겠습니다.

  • Call Stack: 동기적인 작업을 실행한다.
  • Web APIs: setTimout, fetch같은 비동기 작업 실행 환경을 제공한다.
  • Microtask Queue: Promise.then, MutationObserver 같은 비동기 작업을 처리한다.
  • Macrotask Queue: setTimout, setInterval, I/O 콜백 등이 등록되고 관리한다.

왜 이런 구조가 된 것인가?

자바스크립트 런타임 동작이 이렇게 복잡한 구조를 가지게 된 이유는 모든 일을 동시에 처리할 때 무조건적으로 발생하는 문제가 있기 때문입니다. 그것은 바로 동시성인데요. 작업 순서가 바뀌고, 데이터가 덮어쓰이거나 손실되는 일이 발생할 수 있습니다.

자바스크립트는 이 동시성 문제를 간단히 처리하기 위해 콜백 함수 방식을 사용하게 되었습니다.

브라우저 비동기 처리

브라우저 HTML 마크업 내에서도 비동기적으로 동작합니다. script태그를 head태그 안에 넣으면, 자바스크립트 파일이 다운로드되고 실행될 때까지 HTML 파싱이 중단됩니다. 사용자가 웹 페이지를 바로 볼 수 없게 됩니다.

async과 defer

해당 속성들은 자바스크립트 파일을 비동기적으로 다운로드하고 실행하게 합니다. HTML 파싱과 자바스크립트 다운로드가 동시에 진행되어 페이지 로딩 속도가 빨라집니다.

<!DOCTYPE html>
<html>
<head>
  <script async src="script1.js"></script>
</head>
</html>Copy

한걸음

이번 포스트는 방학동안 하고 있는 블로그 스터디의 일종으로 작성하게 되었어요. 각자 블로그를 써 온 다음, 만나서 발표를 하고 유튜브 채널에도 올리고 있습니다. 덕분에 블로그를 더 열심히 쓰게 되네요. 유튜브 채널 링크는 아래를 봐 주시면 감사하겠습니다.

Hangeoreum Youtube

참고

Javascript 비동기 함수의 동작원리 (feat. EventLoop)
자바스크립트의 핵심 '비동기' 완벽 이해

profile
FrontEnd Developer

0개의 댓글

관련 채용 정보