동기(Synchronous)와 비동기(Asynchronous)

eunbi·2025년 2월 2일

CS 총정리

목록 보기
14/22

자바스크립트에서 동기(Synchronous)와 비동기(Asynchronous)작업을 처리하는 방식을 나타낸다.

이 개념을 이해하면 코드 실행 흐름을 더 효과적으로 관리할 수 있으며, 특히 네트워크 요청, 파일 읽기, UI 업데이트 등의 비동기 작업을 다룰 때 매우 중요하다.


1. 동기(Synchronous)란?

📌 동기 실행 방식코드가 순차적으로 실행되며, 하나의 작업이 끝나야 다음 작업이 실행되는 방식이다.

특징

✔️ 코드가 작성된 순서대로 실행

✔️ 하나의 작업이 끝날 때까지 다음 코드가 실행되지 않음 (Blocking)

✔️ 실행 흐름이 직관적이며 예측하기 쉬움

예제

console.log("A");
console.log("B");
console.log("C");

💡 실행 결과 → A → B → C

모든 코드가 작성된 순서대로 실행된다.

문제점

동기 방식은 하나의 작업이 끝날 때까지 다음 작업이 실행되지 않기 때문에 시간이 오래 걸리는 작업이 있을 경우 전체 프로그램이 멈출 수 있다.

console.log("A");
for (let i = 0; i < 1e9; i++) {} // 오래 걸리는 작업
console.log("B");

💡 "A"를 출력한 후, 반복문이 끝날 때까지 "B"는 출력되지 않는다.

즉, CPU가 한 가지 작업에 묶여 다른 작업을 처리하지 못하는 문제(Blocking)가 발생한다.


2. 비동기(Asynchronous)란?

📌 비동기 실행 방식작업을 백그라운드에서 실행하며, 현재 작업이 끝나지 않아도 다음 작업을 계속 실행하는 방식이다.

특징

✔️ 작업을 동시에 처리할 수 있음 (Non-blocking)

✔️ 실행 순서를 예측하기 어려울 수 있음

✔️ 주로 네트워크 요청, 파일 읽기, 타이머(setTimeout) 같은 작업에서 사용됨

비동기 예제 (setTimeout)

console.log("A");

setTimeout(() => {
  console.log("B");
}, 2000);

console.log("C");

💡 실행 결과 → A → C → (2초 후) B

setTimeout이 실행될 때 바로 멈추지 않고 다음 코드(C)를 실행한 후, 지정된 시간(2초)이 지나면 B가 실행된다.

비동기 작업의 처리 방식

자바스크립트는 싱글 스레드(Single Thread)이지만, 비동기 작업을 효율적으로 관리하기 위해 이벤트 루프(Event Loop)와 콜백 큐(Callback Queue)를 사용한다.

  • 시간이 걸리는 작업(예: setTimeout, fetch)은 웹 API가 실행 후 백그라운드에서 처리
  • 완료된 작업은 콜백 큐(Callback Queue)에 저장
  • 콜 스택(Call Stack)이 비면 이벤트 루프(Event Loop)가 콜백 큐의 작업을 실행

3. 비동기 처리를 위한 주요 패턴

1) 콜백 함수(Callback Function)

📌 콜백 함수는 특정 작업이 끝난 후 실행되는 함수이다.

function fetchData(callback) {
  setTimeout(() => {
    console.log("데이터 받아오기 완료");
    callback();
  }, 2000);
}

fetchData(() => {
  console.log("데이터를 화면에 표시");
});

💡 실행 결과 → (2초 후) 데이터 받아오기 완료 → 데이터를 화면에 표시

콜백을 활용하면 비동기 작업이 끝난 후 원하는 작업을 실행할 수 있다.

콜백 지옥(Callback Hell) 문제

콜백 함수가 중첩될 경우 코드가 복잡해지고 가독성이 떨어지는 문제가 발생한다.

setTimeout(() => {
  console.log("1초 후 실행");
  setTimeout(() => {
    console.log("2초 후 실행");
    setTimeout(() => {
      console.log("3초 후 실행");
    }, 1000);
  }, 1000);
}, 1000);

💡 콜백이 중첩되면서 코드가 읽기 어려워지는 문제 발생

이 문제를 해결하기 위해 Promiseasync/await이 등장했다.


2) Promise

📌 Promise는 비동기 작업의 결과(성공 또는 실패)를 나타내는 객체이다.

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("데이터 받아오기 완료");
    }, 2000);
  });
};

fetchData().then((result) => {
  console.log(result);
}).catch((error) => {
  console.log("에러 발생", error);
});

💡 실행 결과 → (2초 후) 데이터 받아오기 완료

  • resolve()가 호출되면 then() 블록이 실행
  • reject()가 호출되면 catch() 블록이 실행

Promise 체이닝 (Chaining)으로 콜백 지옥 해결

fetchData()
  .then((data) => {
    console.log(data);
    return "추가 데이터 처리";
  })
  .then((message) => {
    console.log(message);
  })
  .catch((error) => {
    console.log("에러 발생", error);
  });

💡 then()을 체이닝하면 가독성이 좋아지고, 순차적인 비동기 작업을 쉽게 처리 가능


3) async / await

📌 async/await은 Promise 기반의 비동기 코드를 더 직관적으로 작성할 수 있도록 도와준다.

const fetchData = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("데이터 받아오기 완료");
    }, 2000);
  });
};

async function getData() {
  console.log("데이터 요청");
  const data = await fetchData();
  console.log(data);
}

getData();

💡 실행 결과 → 데이터 요청 → (2초 후) 데이터 받아오기 완료

  • await 키워드를 사용하면 비동기 작업이 완료될 때까지 기다린 후 다음 코드 실행
  • try...catch를 사용하면 에러 처리를 쉽게 할 수 있음

async/await의 에러 처리

async function getData() {
  try {
    const data = await fetchData();
    console.log(data);
  } catch (error) {
    console.log("에러 발생", error);
  }
}

💡 try...catch를 활용해 에러를 직관적으로 처리 가능


4. 동기 vs 비동기 비교

동기(Synchronous)비동기(Asynchronous)
실행 방식코드가 순차적으로 실행됨작업이 완료되지 않아도 다음 코드 실행 가능
작업 흐름한 번에 하나의 작업만 수행 (Blocking)여러 작업을 동시에 처리 가능 (Non-blocking)
사용 예시console.log(), 일반 연산setTimeout(), fetch(), Promise
문제점긴 작업이 있으면 전체 실행이 멈춤실행 순서가 예측하기 어려울 수 있음
해결 방법멀티스레딩 필요Promise, async/await

📌 결론

✔️ 동기 방식은 실행 순서가 직관적이지만 시간이 오래 걸리는 작업에서 블로킹 발생

✔️ 비동기 방식은 작업을 동시에 실행하여 UI 반응성을 높이고 성능을 최적화

✔️ 콜백 함수 → Promise → async/await 순으로 발전하여 더 직관적인 비동기 코드 작성 가능 🚀

0개의 댓글