JS 동기, 비동기

Sei·2025년 9월 1일
1
post-thumbnail

동기 vs. 비동기

동기 (Synchronous)
한 줄의 코드가 실행을 마쳐야 그 다음 줄이 실행되는 방식이다.
순차적, 직렬 처리 라고 생각하면 된다.

비동기 (Asynchronous)
특정 작업이 끝날 때까지 기다리지 않고,
나머지 코드들을 먼저 실행시키는 방식이다.
병렬 처리, 이벤트 기반 실행이라고 생각하면 된다.

예시 코드

동기 예시 코드

console.log("1. 시작");
console.log("2. 처리 중...");
console.log("3. 끝");
1. 시작
2. 처리 중...
3. 끝

➡️ 위에서부터 순차적으로 실행되는 것을 확인할 수 있다.

비동기 예시 코드

console.log("1. 시작");

setTimeout(() => {
  console.log("2. 처리 중...(2초 후)");
}, 2000);

console.log("3. 끝");
1. 시작
3. 끝
2. 처리 중...(2초 후)

➡️ setTimeout은 비동기 처리라서, 2초가 지나야 실행된다.
➡️ 하지만 그동안 코드 실행은 멈추지 않고 바로 3. 끝이 출력된다.

왜 비동기가 필요할까?

JS는 싱글 스레드 언어라서 동기만 사용하면 긴 작업(예: 서버 요청, 파일 읽기) 때문에 전체 프로그램이 멈추게 된다.

비동기를 사용하면:

➡️ 서버 응답을 기다리면서도 다른 UI 동작이 가능하고
➡️ 이로 인해 사용자 경험(UX) 개선이 가능하다!

싱글 스레드 언어

스레드(Thread)란?

  • 스레드는 CPU가 일을 처리하는 실행 단위

  • 쉽게 말하면 한 명의 일꾼이라고 생각하면 된다!

  • 여러 스레드가 있으면 여러 명의 일꾼이 동시에 일을 나눠서 할 수 있고, 스레드가 하나라면 일꾼은 한 명뿐이다...!

자바스크립트는 왜 싱글 스레드일까?

자바스크립트는 하나의 스레드(=한 명의 일꾼)만 가지고 있다.

즉, 한 번에 하나의 작업만 실행할 수 있다.

이 때문에 자바스크립트에서는 긴 작업이 있으면
프로그램 전체가 멈춰버릴 위험이 있다! (그래서 비동기 처리가 중요)

그런데 왜 비동기 처리가 가능할까?

자바스크립트가 싱글 스레드임에도 불구하고 비동기 동작이 가능한 이유는 브라우저(또는 Node.js) 환경 덕분이다.

브라우저/Node.js는 Web API / 백그라운드 스레드를 따로 가지고 있다.

자바스크립트 엔진은 한 명의 일꾼이지만,
무거운 일(예: setTimeout, fetch 요청)은 브라우저에게 맡기고,
일이 끝나면 이벤트 루프(Event Loop)가 “이제 처리해!” 하고
다시 자바스크립트에게 알려준다.

  • 싱글 스레드 = 카페 직원이 한 명뿐인 상황

  • 주문받기, 커피 만들기, 계산하기… 전부 그 한 명이 처리해야 하는 상황

  • 그런데 사장이 커피 기계(비동기 API)를 따로 줌
    → 직원은 주문만 넣어두고 다른 손님 응대 가능
    → 커피가 다 되면 기계가 알려줌
    → 직원은 다시 서빙

이벤트 루프(Event Loop)

자바스크립트가 비동기 동작을 할 수 있는 핵심은 바로
이벤트 루프(Event Loop) 구조 덕분이다.

이벤트 루프는 크게 네 가지 요소로 이루어진다!

Call Stack (콜 스택) → 실행 중인 코드가 쌓이는 곳

Web API → 브라우저/Node.js가 제공하는 비동기 작업 공간
(setTimeout, fetch, 이벤트 등)

Task Queue (태스크 큐) → 완료된 콜백들이 대기하는 줄

Event Loop (이벤트 루프) → 콜 스택이 비면, 태스크 큐에서 대기 중인 작업을 가져와 실행

console.log("1. 시작");

setTimeout(() => {
  console.log("2. 타이머 끝");
}, 0);

console.log("3. 끝");


출력 결과

1. 시작
3. 끝
2. 타이머 끝

➡️ setTimeout의 콜백은 바로 실행되지 않고,
Web API → Task Queue → Event Loop → Call Stack 순서로 이동한 뒤 실행된다.


카페로 비유한다면?

Call Stack = 직원이 지금 처리 중인 주문

Web API = 커피 기계 (타이머, 서버 요청 등)

Task Queue = 다 된 커피가 대기하는 선반

Event Loop = 직원이 선반을 확인하고, 손이 비면 커피를 손님에게 내주는 과정

비동기 처리 방식

1. 콜백 (Callback)

옛날 방식으로, 함수의 결과를 다른 함수에 전달하는 구조
여러 개가 중첩되면 콜백 지옥(Callback Hell) 문제가 생긴다.

console.log("데이터 요청 시작");

setTimeout(() => {
  console.log("1차 응답 처리");
  setTimeout(() => {
    console.log("2차 응답 처리");
    setTimeout(() => {
      console.log("3차 응답 처리 완료");
    }, 1000);
  }, 1000);
}, 1000);


출력:

데이터 요청 시작
1초 후 → 1차 응답 처리
2초 후 → 2차 응답 처리
3초 후 → 3차 응답 처리 완료

➡️ 가독성이 떨어지고, 유지보수가 어렵다.

2. Promise

콜백 대신 체이닝(.then, .catch)을 통해 순서를 제어할 수 있다.

console.log("데이터 요청 시작");

new Promise((resolve) => {
  setTimeout(() => {
    console.log("1차 응답 처리");
    resolve();
  }, 1000);
})
.then(() => new Promise((resolve) => {
  setTimeout(() => {
    console.log("2차 응답 처리");
    resolve();
  }, 1000);
}))
.then(() => {
  setTimeout(() => {
    console.log("3차 응답 처리 완료");
  }, 1000);
});

➡️ 중첩은 줄었지만 .then 체인이 길어지면 여전히 복잡할 수 있다.

3. async / await

최신 방식으로, 마치 동기 코드처럼 읽히지만 내부는 비동기이다!

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function process() {
  console.log("데이터 요청 시작");

  await delay(1000);
  console.log("1차 응답 처리");

  await delay(1000);
  console.log("2차 응답 처리");

  await delay(1000);
  console.log("3차 응답 처리 완료");
}

process();

➡️ 코드가 깔끔하고, 가독성이 높아져서 실무에서 가장 많이 사용된다.

정리

  • 동기: 직렬, 순차 실행 → 이해하기 쉽지만 효율이 낮다.

  • 비동기: 병렬처럼 동작 → 효율적, UX 개선이 가능하다.

카페에서 커피 주문을 한다고 가정해보자!

  • 동기: 내가 커피 받을 때까지 줄 서서 기다림

  • 비동기: 주문하고 자리에 앉아 다른 일 하다가,
    커피 나오면 알림 받고 가지러 감

JS에서는 비동기 프로그래밍이 필수적 → 주로 async/await을 사용

  • 콜백 : 단순하지만 중첩되면 가독성↓

  • Promise : 체이닝으로 개선했지만 여전히 복잡 가능

  • async/await : 동기처럼 작성 가능, 가장 깔끔

profile
front-end developer

0개의 댓글