CS 과제 정리
[CS] JavaScript의 비동기 처리: Promise와 async/await 완벽 가이드
thisnorm
2025. 2. 2. 16:59
1. Promise에 대하여
1-1. Promise의 정의
📌Promise란
Promise 는 JavaScript에서 비동기 작업의 결과를 나타내는 객체이다. 비동기 작업이 완료되면 Promise 는 결과를 소비하는 코드에 결과를 전달한다.
1-2. Executor 함수
- Promise는 new Promise를 통해 생성되며, 이때 전달되는 함수가 executor이다.
- executor는 자동으로 실행되며, 비동기 작업을 수행한다.
- executor는 두 개의 인수인 resolve와 reject를 받는다.
- 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를 추가하는 것이 일반적이다.