본문 바로가기
CS 질문

[Deep Dive CS - Q24] - Promise VS Async/Await

by 민챙이_99 2025. 12. 18.

Q24. ES6의 Promise 체이닝 방식과 ES8의 Async/Await 방식의 가장 큰 차이점은 무엇인가요?

 

1. 에러 핸드링의 통합(Error Handling)

가장 큰 기술적 차이는 "동기 에러와 비동기 에러를 한곳에서 처리 할 수 있는가?" 입니다. 

  • Promise: .catch()는 Promise 체인 안에서 발생한 에러만 잡습니다. 만약 체인 내부가 아닌 곳에서 에러가 발생하면, .catch가 못잡고 프로세스가 죽을 수도 있습니다. 
  • Async/Await : try/catch 문법을 사용하므로, 네트워크 에러(비동기)와 데이터 파싱 에러(동기)를 하나의 블록에서 완벽하게 잡아냅니다
// [Promise] 에러 처리가 분산됨
function loadData() {
  try { // 동기 에러용 try
    getData()
      .then(json => {
        const data = JSON.parse(json); // 여기서 에러나면 .catch로 감
        return data;
      })
      .catch(e => { // 비동기 에러 처리
        console.log('Promise Error', e);
      });
  } catch (e) { // 동기 에러 처리 (Promise 밖의 에러)
    console.log('Sync Error', e);
  }
}

// [Async/Await] 모든 에러를 한 방에 처리 (Good)
async function loadData() {
  try {
    const json = await getData(); // 비동기 에러 발생 가능
    const data = JSON.parse(json); // 동기 에러 발생 가능 (둘 다 여기서 잡힘)
    return data;
  } catch (e) {
    console.log('All Error', e);
  }
}

 

2. 스코프(Scope)와 변수 공유

복잡한 비동기 로직을 짤 때 "변수 전달" 문제에서 큰 차이가 납니다. 

  • Promise: 체이닝이 길어지면, 첫 번재 .then에서 받은 결과(user)를 세번째 .then에서 쓰기 위해 계속 리턴으로 넘겨주거나 전역변수를 파야 하는 Props Drilling 같은 귀찮음이 발생합니다. 
  • Async/Await: 함수 내부가 하나의 스코프이므로, 상단에 선언한 변수를 어디서든 자유롭게 접근할 수 있습니다. 이것 때문에 코드가 깔끔해집니다. 
// [Promise] user1을 저 밑에서 쓰려면 계속 넘겨줘야 함 (귀찮음)
fetchUser(1)
  .then(user1 => {
    return fetchUser(2).then(user2 => [user1, user2]); // user1을 안 잃어버리려고 클로저나 배열 사용
  })
  .then(([user1, user2]) => {
    console.log(user1, user2);
  });

// [Async/Await] 그냥 변수 선언하면 끝 (깔끔)
const user1 = await fetchUser(1);
const user2 = await fetchUser(2);
console.log(user1, user2); // 자유롭게 사용

 

3. 디버깅과 콜스택(Debug & Call Stack)

  • Promise : .then 내부의 콜백함수는 비동기로 실행되므로, 에러가 났을 때 콜 스택(Call Stack)에 이전 함수들의 정보가 명확히 남지 않을 때가 많습니다. (익명 함수로 처리되기도 함)
  • Async/Await: 엔진이 await 지점의 컨텍스트를 저장해두기 때문에, 에러가 터졌을 때 "어떤 함수에서 호출하다가 죽었는지" 스택 트레이스(Stack Trace)가 훨씬 명확하게 나옵니다. 

 

=> 해당 부분에 대해서 테스트를 진행해보면, 둘다 동일하게 에러 추적이 잘되는 것을 볼 수 있습니다. 
이는 V8엔진의 Zero-cost async stack traces라는 기능 때문에, Promise 체이닝도 가능한 스택을 복구해줄 수 있도록 엔진이 support 해주기 때문입니다.  
하지만, setTimeout과 같이 call stack 외부에서 실행되는 순간 실행컨텍스트가 이벤트 루프의 다음 바퀴로 넘어가면서 스택에 남아있는 정보가 아무것도 없기 때문에 Async/Await을 써도 엔진이 문백을 잃어버리는 것입니다. 

 

 

"Promise도 스택이 잘 나오는데 굳이 Async/Await를?"

"V8 엔진이 Promise 스택도 잘 복구해 주는 건 맞지만, 복잡한 제어 구문(for문, if문) 안에서의 비동기 처리는 Async/Await가 압도적으로 유리합니다.

Promise로 반복문을 짜면 재귀를 돌거나 복잡한 체이닝을 해야 해서 스택이 꼬이기 쉽지만, Async/Await는 언어 차원에서 문맥(Context)을 블록 단위로 보존해주기 때문에 디버깅 난이도가 훨씬 낮습니다."

 

 

[Promise VS Async/Await 스택 차이]
- Promise비동기 작업이 끝날 때마다 새로운 콜 스택이 생성됩니다. 이전 주자는 배턴(결과값)만 넘겨주고 퇴근해버리는 방식입니다. 

더보기

[Time 1] funcA -> funcB 호출
Stack: [funcA, funcB]

[Time 2] Promise 리턴 직후
Stack: []  <-- 텅 빔! (맥락 단절)

[Time 3] .then() 실행 시점
Stack: [Anonymous Callback] <-- 뜬금없이 혼자 실행됨
(V8이 억지로 [funcA -> funcB] 정보를 붙여줌)

- Async/Await : 제너레이터(Generator) 기반이므로, 함수가 리턴하고 사라지는 게 아니라 메모리 힙(Heap) 어딘가에 상태를 저장해두고 '일시 정지'합니다.

더보기

[Time 1] funcA -> funcB 호출
Stack: [funcA, funcB]

[Time 2] await 만남
Stack: [] (비워지지만, funcA의 컨텍스트는 메모리에 살아있음)

[Time 3] funcB 완료 후 복귀
Stack: [funcA] <-- 되살아남! (Resume)
       └-> [funcB 결과 처리]

사실 Async/Await는 제너레이터(Generator, function*)의 문법적 설탕(Sugar)입니다. 

제너레이터가 실행될 때 생성되는 Iterator 객체가, 함수 내부의 모든 로컬 변수와 실행 위치를 객체 형태로 캡슐화해서 들고 있습니다. 

실행컨텍스트 객체를 통해 힙에 저장해 두었다가 복구하는 기술입니다. 

 

그러면, Async/Await는 함수가 끝난 뒤에도 변수가 살아있다는 부분에 대해서 클로저하고 비슷한것 같습니다.
그러면 Async/Await도 클로저라고 말할 수 있나요? 

 

결론부터 말씀드리자면, 비슷한 부분이 많기는 하지만 같은 기술은 아닙니다

클로저는 함수가 리턴되어 완전히 종료 되었는데, 내부의 변수만 힙(Heap)메모리 어딘가에 따로 참조해서 살아남은 것입니다. 
=> 함수는 죽었고, 변수만 Heap에 저장되어 있는 상태

 

Async/Await은 함수가 종료된 게 아니라, 일시 정지 상태로 메모리에 함수 전체(실행 컨텍스트)가 통째로 보존되어 있는 것입니다. 

=> 함수는 안죽었고, 그냥 얼음 상태입니다.

(사진=넷플릭스 '오징어 게임')

 

4. 결정적 차이 : Non-blocking의 시각화

  • Promise: 코드가 .then으로 분리되어 있습니다. 
  • Async/Await: 코드가 위에서 아래로 흐르니 동기 코드처럼 착각하기 쉽습니다. 
    - 주의점 : await는 Blocking 처럼 보이지만, 실제로는 Main Thread를 차단하지 않습니다. await를 만나는 순간 함수 실행을 멈추고 제어권을 이벤트 루프에게 넘겨줘서 다른 작업을 할 수 있게 해줍니다. 

'CS 질문' 카테고리의 다른 글

[Deep Dive CS - Q27] 코드리뷰  (0) 2025.12.22
[Deep Dive CS - Q25,Q26] Promise  (0) 2025.12.18
[Deep Dive CS - Q23] Class  (0) 2025.12.17
[Deep Dive CS - Q22] Prototype Chain  (0) 2025.12.17
Deep Dive CS 질문 리스트 2  (0) 2025.12.17