Language & Framework

[JavaScript] Synchronous / Asynchronous

코딩 기록하는 애기 개발자 2025. 8. 24. 21:19

목차

    NodeJS 로 프로젝트를 개발할 때

    javascript
    export const function = async(parameter) => { ... }

     

    위 코드처럼 async 를 습관적으로 작성했었다. 사실 나는 해당 키워드가 비동기 처리를 해준다 라는 것만 알고, 자세히 들여다보진 않았었다. 하지만, NodeJS에 대해서 정리를 하다보니까 습관처럼 사용하던 async라는 키워드에 대해서 궁금증이 생겼고, 동기/비동기 에 대해서 알아보았다.

     

     

    동기 (Synchronous) vs 비동기 (Asynchronous)

    동기 (Synchronous)

    - 한 줄이 끝나야 다음 줄이 실행된다

    - CPU 계산처럼 바로 끝나는 작업에 적합하다.

     

    예시 )

    const a = 1 + 1;       // 바로 계산됨  
    const b = a * 2;       // a가 끝나야 실행  
    console.log(b);

     

     

    비동기 (Asynchronous)

    - 오래 걸리는 작업을 기다리지 않고 다음 코드로 넘어간다.

    - I/O 작업 (네트워크 요청, 파일 읽기, DB) 등에 적합하다.

    - 완료 시점에 callback / Promise 를 통해 결과를 알려준다.

     

    예시 )

    function getNumber() {
      return Promise.resolve(2); // 바로 2를 반환하는 비동기 함수
    }
    
    async function run() {
      const a = await getNumber(); // a 값이 준비될 때까지 기다림
      const b = a * 2;             // a가 끝난 뒤 실행
      console.log(b);              // 4
    }
    
    run();

     

    비동기를 습관처럼 사용하는 이유가 뭘까 ?

     

    일단 첫 번째 장점은 성능 향상이다.

    비동기를 사용하면 오래 걸리는 작업 때문에 프로그램 전체가 멈추는 현상을 줄일 수 있다.

    예를 들어, 내가 카페에 갔는데 앞 손님이 아아 100잔을 테이크아웃 주문했다고 해보자.

    그렇다고 해서 내가 앞 손님이 100잔을 다 받을 때까지 기다린다면 너무 비효율적일 것이다.

    보통 이런 상황에서는 카페가 아아 100잔을 만드는 동안에도 다른 손님들의 주문을 함께 받는다.

    이처럼 여러 작업을 동시에 처리하는 방식을 비동기 처리라고 한다.

    또 다른 장점은 병렬적 작업 처리다.

    다시 카페 상황을 생각해보면, 아아 100잔과 뒤에 들어오는 주문을 동시에 처리하려고 해도

    직원이 한 명뿐이라면 감당하기 어렵다.

    그래서 여러 알바생을 고용해 각자 작업을 분담한다면 훨씬 빠르게 주문을 처리할 수 있다.

    이처럼 여러 개의 작업을 동시에 나눠서 처리하는 과정을 병렬 처리라고 한다.

     

    콜백 (Callback)  & 프로미스 (Promise)

    비동기 처리가 끝나면 결과를 알려주는 방법에는 두 가지가 있다 : Callback & Promise

    이 두가지 방법에 대해서 알아보도록 하자.

     

    콜백 (Callback)

    : 비동기 함수에 작업이 끝나면 실행할 함수를 전달하는 방식이다.

    function getData(callback) {
      setTimeout(() => {
        const data = "서버에서 온 데이터";
        callback(data);  // 완료되면 callback 실행
      }, 2000);
    }
    
    getData((result) => {
      console.log("결과:", result);
    });
    // 실행 흐름: (즉시) → 대기 → 2초 뒤 "결과: 서버에서 온 데이터"

     

    사용법이 굉장히 간단하다는 장점이 있지만, 콜백 안에서 또 다른 콜백을 사용하면 콜백 지옥 (Callback Hell) 문제가 발생할 수 있다.

     

     

    프로미스 (Promise)

    콜백 방법 보다는 조금 더 구조화된 방법으로, 비동기 작업이 끝나면 resolve (성공) 또는 reject (실패)를 호출해서 결과를 알려준다.

    function getData() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          const data = "서버에서 온 데이터";
          resolve(data);  // 성공 시점
        }, 2000);
      });
    }
    
    getData()
      .then((result) => {
        console.log("결과:", result);
      })
      .catch((error) => {
        console.error("에러:", error);
      });
    • resolve : Promise 안에서 성공적으로 작업이 끝났음을 알리는 함수
    • reject : Promise 안에서 작업이 실패했음을 알리는 함수
    • then : Promise 안에서 비동기 작업이 끝난 후 (성공을 했을 때), 실행될 동작을 등록하는 함수

     

    async / await

    이 방법이 가장 많이 사용하는 방법이다. 나도 이 방법을 거의 공식처럼 사용하기도 했다. 이 방법은 Promise의 진화형으로 생각하면 좋을 것 같다. 위 Promise 코드를 조금 더 동기 코드처럼 볼 수 있도록 해주는 문법이다.

    async function main() {
      try {
        const result = await getData(); // getData는 Promise 반환
        console.log("결과:", result);
      } catch (error) {
        console.error("에러:", error);
      }
    }
    
    main();

     

    위 코드를 보면, 이 전 Promise의 코드보다 훨씬 더 읽기 쉽다.

     

    그러면 병렬처리는 어떻게 처리할 수 있을까 ?

    // 여러 사용자를 병렬(동시에)로 불러오는 함수
    async function getMultipleUsersParallel(ids) {
     
      const promises = ids.map((id) => fetchUser(id));
      const results = await Promise.all(promises);
    
      return results;
    }

     

    이 코드는 비동기 요청을 병렬처리로 처리하는 코드이다.

     

    차근 차근 코드를 설명해보자면,

    ids 라는 배열이 있을 때, map을 돌면서 각 id 마다 `fetchUser(id)` 라는 함수를 실행한다.

    이때, `fetchUser(id)`는 비동기 함수이기 때문에 Promise 객체를 반환한다.

    이후  `Promise.all([Promise 배열])`라는 함수를 이용하여 `promises` 배열에 있는 promise 객체들을 동시에 실행시킨다.

    이들이 모두 완료될 때까지 대기 했다가 완료가 되면 모든 결과를 배열로 반환한다.

     

     

     

     


     

     

     

     

    비동기 처리는 Node.js에서 정말 중요한 개념이다.  callback → Promise → async/await 순으로 발전해오면서 코드의 가독성과 유지보수성이 크게 좋아졌다. 특히 async/await 은 겉으로 보기엔 동기 코드처럼 읽히지만, 실제로는 비동기 처리를 해주기 때문에 내가 무심코 습관처럼 써왔던 이유를 이제야 명확히 이해할 수 있었다.