CS 과제 정리

[CS] JavaScript의 비동기 처리: Promise와 async/await 완벽 가이드

thisnorm 2025. 2. 2. 16:59

1. Promise에 대하여

1-1. Promise의 정의

📌Promise란

Promise 는 JavaScript에서 비동기 작업의 결과를 나타내는 객체이다. 비동기 작업이 완료되면 Promise 는 결과를 소비하는 코드에 결과를 전달한다.

 

 

1-2. Executor 함수

  • Promisenew Promise를 통해 생성되며, 이때 전달되는 함수가 executor이다.
  • executor는 자동으로 실행되며, 비동기 작업을 수행한다.
  • executor는 두 개의 인수인 resolvereject를 받는다.
    • resolve(value) : 작업이 성공적으로 완료되었을 때 호출된다.
    • reject(error) : 작업이 실패했을 때 호출된다.

 

1-3. Promise의 상태

📌Promise 는 세 가지 상태를 가진다.

  • Pending: 초기 상태, 작업이 아직 완료되지 않음
  • Fulfilled: 작업이 성공적으로 완료되었을 때
  • Rejected: 작업이 실패했을 때

상태가 변화하면 더 이상 변경되지 않으며, 이후에는 결과를 소비하는 메서드를 통해 결과를 처리할 수 있다.

 

 

1-4. 결과 처리 방법

Promise 의 결과를 처리하기 위해 다음과 같은 메서드를 사용한다.

  • then
    • 문법: promise.then(onFulfilled, onRejected)
    • 첫 번째 인수는 작업이 성공했을 때 실행되는 함수, 두 번째 인수는 실패했을 때 실행되는 함수이다.
  • catch
    • 문법 promise.catch(onRejected)
    • 작업이 실패했을 때만 실행되는 함수이다. then의 두 번째 인수와 동일하게 작동한다.
  • finally
    • 문법: promise.finally(onFinally)
    • 작업이 완료된 후(성공 또는 실패) 항상 실행되는 함수입니다. 인수를 받지 않으며, 결과와 에러를 전달하지 않습니다.

 

1-5. Promise의 장점

  • 비동기 작업의 흐름을 자연스럽게 처리할 수 있습니다.
  • 여러 개의 핸들러를 등록할 수 있어 유연한 코드 작성이 가능합니다.
  • 콜백 지옥을 피할 수 있습니다.

2. Promise와 에러 처리에 대한 정리

2-1. Promise의 거부와 에러 처리

  • Promise가 거부되면 제어 흐름은 가장 가까운 rejection 핸들러로 넘어간다. 이는 실무에서 에러를 쉽게 처리할 수 있는 유용한 기능이다.
  • 예시
fetch('https://no-such-server.blabla') // 거부  
  .then(response => response.json())  
  .catch(err => alert(err)); // TypeError: failed to fetch

 

  • .catch는 여러 개의 .then뒤에 올 수 있으며, 첫 번째 핸들러일 필요는 없다.

 

 

2-2. JSON 형식 오류 처리

  • 정상적인 경우에는 .catch가 트리거되지 않지만, 네트워크 문제가 잘못된 JSON형식으로 인해 위쪽 프라미스 중 하나라도 거부되면 .catch에서 에러를 잡게 된다.
  • 예시
fetch('/article/promise-chaining/user.json')  
  .then(response => response.json())  
  .then(user => fetch(`https://api.github.com/users/${user.name}`))  
  .then(response => response.json())  
  .then(githubUser => {  
    let img = document.createElement('img');  
    img.src = githubUser.avatar_url;  
    img.className = "promise-avatar-example";  
    document.body.append(img);  
    setTimeout(() => {  
      img.remove();  
      resolve(githubUser);  
    }, 3000);  
  })  
  .catch(error => alert(error.message));

 

 

2-3. 암시적 try..catch

  • Promise의 executor와 핸들러 코드 주위에는 ‘암시적 try..catch’가 존재한다. 예외가 발생하면 이 블록에서 예외를 잡고 이를 reject처럼 다룬다.
  • 예시
new Promise((resolve, reject) => {  
  throw new Error("에러 발생!");  
}).catch(alert); // Error: 에러 발생!

 

 

2-4. 에러 다시 던지기

  • .catch 안에서 throw 를 사용하면 제어흐름이 가장 가까운 에러 핸들러로 넘어간다.
  • 예시
new Promise((resolve, reject) => {  
  resolve("OK");  
}).then(result => {  
  throw new Error("에러 발생!"); // 프라미스가 거부됨  
}).catch(alert); // Error: 에러 발생!

 

 

2-5. 처리되지 않은 거부

  • 에러를 처리하지 못하면, 프라미스는 거부 상태가 되고 실행 흐름은 가장 가까운 rejection 핸들러로 넘어간다. 핸들러가 없으면 에러가 ‘갇혀버린다’.
  • 브라우저 환경에서는 unhandledrejection 이벤트로 처리되지 않은 에러를 추적할 수 있다.
  • 예시
window.addEventListener('unhandledrejection', function(event) {  
  alert(event.promise); // [object Promise]  
  alert(event.reason); // Error: 에러 발생!  
});

 

 

2-6. 요약

  • .catch는 프라미스에서 발생한 모든 에러를 처리한다.
  • 에러 발생 시, 회복할 방법이 없다면 .catch를 사용하지 않아도 괜찮다.
  • unhandledrejection 이벤트 핸들러를 사용해 처리되지 않은 에러를 추적하고 사용자에게 알리는 것이 중요하다.

 


 

3. async와 await에 대하여

3-1. async와 await의 기본 개념

  • async await 는 JavaScript에서 비동기 프로그래밍을 더 쉽게 만들어주는 문법이다.
  • async 키워드는 함수 앞에 위치하며, 해당 함수는 항상 프라미스를 반환한다.

 

 

3-2. async 함수의 반환값

  • async 함수는 항상 프라미스를 반환한다. 예를 들어, 아래의 함수는 이행된 프라미스를 반환한다.
async function f() {  
    return 1;  
}  
f().then(alert); // 1

 

 

3-3. await의 사용

  • await는 프라미스가 이행될 때까지 대기한다. await가 붙은 줄에서 실행이 잠시 중단되며, 프라미스가 처리되면 실행이 재개된다.
  • 예시
async function f() {  
    let promise = new Promise((resolve) => {  
        setTimeout(() => resolve("완료!"), 1000);  
    });  
    let result = await promise; // 프라미스가 이행될 때까지 대기  
    alert(result); // "완료!"  
}  
f();

 

 

3-4. 에러 핸들링

  • await가 있는 줄에서 프라미스가 거부되면, 마치 throw문을 사용한 것처럼 에러가 발생한다.
  • 에러가 try..catch를 사용하여 처리할 수 있다.
async function f() {  
    try {  
        let response = await fetch('http://유효하지-않은-주소');  
    } catch (err) {  
        alert(err); // TypeError: failed to fetch  
    }  
}  
f();

 

 

3-5. Promise.all과의 조합

  • 여러 개의 프라미스를 동시에 처리해야할 때 Promise.all을 사용할 수 있다. 이때 await를 붙여서 모든 프라미스가 처리될 때까지 대기할 수 있다.
let results = await Promise.all([  
    fetch(url1),  
    fetch(url2),  
    // ...  
]);

 

 

3-6. async/await의 장점

  • async/await를 사용하면 코드가 더 깔끔하고 읽기 쉬워진다. 비동기 처리를 더 직관적으로 작성할 수 있다.
  • try..catch를 사용하여 에러를 처리할 수 있어, .then.catch를 사용하는 것보다 더 간편하다.

 

3-7. 문법 제약

  • async함수 바깥의 최상위 레벨 코드에서는 await를 사용할 수 없다. 따라서 최종 결과나 처리되지 않은 에러를 다루기 위해 .then/catch를 추가하는 것이 일반적이다.