DYO 공부하는 블로그
[JS] Promise 예시와 함께 이해해보기 본문
JS의 Promise는 비동기 실행을 처리하기 위한 객체이다. 일단 비동기 처리부터 차근차근 이해해보자.
비동기 처리 :
순서대로 처리되는 동기 코드와 달리 비동기 코드는 실행됐을 때 이 코드가 언제 완료될 지 명확하지 않은 코드를 의미한다. 일반적으로 웹에서는 서버 통신을 받아야 하는 경우 비동기처리를 해야 한다. JS에서 비동기처리는 Callback, Promise, async/await을 이용해 처리한다.
Promise란?
객체는 비동기 처리를 담당하는 약속 객체다. 프로미스의 내부의 값은 then, catch, finally 등의 내부에서 확인할 수 있으며, 프로미스 객체의 then 체이닝을 이용해 비동기 처리를 순서대로 수행하도록 한다.
function weed(){
return new Promise((resolve, reject)=>{
if("다 뽑았다.") resolve();
else reject();
})
}
weed()
.then(() => console.log('그럼 집에 가자'))
.catch(() => console.log('가긴 어딜 가'))
Promise 객체를 사용할 때 중요한 부분은 new 생성자 키워드를 이용해 프로미스 객체를 생성해주어야 한다는 점이다. 특히 비동기 처리를 수행하는 함수의 경우에는 return 해줄 때의 위의 형식이 일반적이라고 생각하면 된다.
콜백 이름은 상관없지만, 앞에 들어오는 resolve()는 처리에 성공했을 때 then 체이닝으로 넘겨주고, reject()는 처리가 실패했을 때 catch 구문으로 then 체이닝을 모두 무시하고 넘어가도록 만든다.
async/await
async/await은 Promise 비동기 처리 구문을 마치 동기 코드처럼 만들어주는 것이다. async 키워드로 선언된 함수 구문에서 await을 사용할 수 있으며, await 키워드가 들어간 처리를 끝낼 때까지 기다렸다가 Promise 객체가 아닌 then 내부에서 처리하는 것처럼 값을 반환한다. (마치 Parser처럼 동작 가능)
async function renderPostings(){
let res = await getPosting() // response 객체 반환 ( 여기까진 Promise 라고 이해 )
let data = await res.json() // data 값 반환 ( 여기서는 json객체 )
/*
렌더링 처리
*/
}
res.json()도 await 처리를 하는 이유는 res.json() 메서드가 Promise 객체를 반환하기 때문에, 그것을 파싱(해석)해 data 구조 객체로 반환해주기 위해서이다.
Promise에서의 return
Promise 객체 내부의 then, catch 메서드 체이닝과 async 함수에서 주의할 점은, 모든 반환값이 Promise로 나온다는 점이다.
function weed(){
return new Promise((resolve, reject)=>{
if(weedPullComplete === "다 뽑았다.") resolve();
else reject();
})
}
let text = weed()
.then(result => '그럼 집에 가자')
.catch(() => {return '가긴 어딜 가'})
console.log(text) // Promise 객체가 나옴
async function getPosting(url, option){
console.log(await req(url, option))
return
}
console.log(getPosting('http://우리 서버:5000', defaultOption)) // Promise 객체가 나옴
모든 반환값이 Promise로 나오기 때문에 내부의 값을 참조하기 위해서는 참조한 async 함수 내부에서 처리하거나, 추가적인 async 함수로 처리되어야 한다.
async function getPosting(url, option){
return req(url, option)
}
async function renderPostings(){
let res = await getPosting() // response 객체 반환 ( 여기까진 Promise 라고 이해 )
let data = await res.json() // data 값 반환 ( 여기서는 json객체 )
/*
렌더링 처리
*/
}
헷갈릴만한 부분 정리
1. 왜 async 내부에서 Callback을 사용할 때 async 키워드를 이용해야 하지?
async function outer() {
setTimeout(function callback() {
// 여기서 await 사용 불가
}, 1000);
}
간단하게 요약하면, this( 현재 실행되고 있는 위치 ) 에서 가장 첫 번째 함수의 키워드가 필요하기 때문
2. 파라미터에 await으로 처리된 Promise를 넣는다면?
async function outer() {
anotherFunc(await getValue());
console.log(await getValue()); // "값"
}
async function getValue() {
return "값";
}
function anotherFunc(arg) {
console.log("arg:", arg); // async 함수가 아닌데 await 값을 참조?
}
함수 구조가 헷갈리니 흐름 구조부터 설명하자면, outer() 실행 → await getValue() 실행 → “값”을 arg로 anotherFunc 실행 → console.log(”arg:”, arg:”값”)이 됨.
파라미터로 await 값이 넘어갔을 때 Promise 객체가 넘어가지 않는 이유는, 그 함수 내부에서는 ‘값’으로 해석된 상태로 사용할 수 있기 때문
Promise 관련 메서드 정리
Promise.all() : 여러 개의 Promise를 동시에 실행하고, 모두 성공했을 때 결과를 배열로 반환.
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(results => {
console.log(results); // [1, 2, 3]
})
.catch(err => {
console.log("실패:", err);
});
Promise.allSettled() : 모든 Promise가 완료될 때까지 기다리고, 성공/실패 여부를 포함한 결과 객체를 배열로 반환. 실패하더라도 모든 결과를 모아줌
const p1 = Promise.resolve(1);
const p2 = Promise.reject("error");
const p3 = Promise.resolve(3);
Promise.allSettled([p1, p2, p3])
.then(results => {
console.log(results);
/*
[
{ status: "fulfilled", value: 1 },
{ status: "rejected", reason: "error" },
{ status: "fulfilled", value: 3 }
]
*/
});
Promise.race() : 여러 Promise 중 가장 먼저 완료된 Promise의 결과를 반환. 가장 먼저 실패하면 실패한 것, 성공하면 성공한 것을 반환해준다.
const p1 = new Promise(resolve => setTimeout(() => resolve("1초"), 1000));
const p2 = new Promise(resolve => setTimeout(() => resolve("0.5초"), 500));
Promise.race([p1, p2])
.then(result => console.log(result)) // "0.5초"
.catch(err => console.log("실패:", err));
Promise.any() : 여러 Promise 중 가장 먼저 fulfill된(성공한) Promise 결과만 반환. 모든 Promise가 실패하면 AggregateError 발생
const p1 = Promise.reject("fail");
const p2 = new Promise(resolve => setTimeout(() => resolve("성공!"), 500));
const p3 = new Promise(resolve => setTimeout(() => resolve("느린 성공"), 1000));
Promise.any([p1, p2, p3])
.then(result => console.log(result)) // "성공!"
.catch(err => console.log("모두 실패:", err));
실제 코드 문제 트러블슈팅하며 이해도 올리기 위한 예제
//
export async function postRender(postDataArray, username = getUserSessionStorage()) {
let myBookmarklist
if (username) {
const response = await getMyBookmarks(username)
const bookmarkData = await response.json()
myBookmarklist = bookmarkData.bookmarklist
}
if (!username) myBookmarklist = []
//렌더할 노드 선택
const tbody = getNode('tbody')
// 왜 Promise.all을 사용해야 할까?
let postHtml = await Promise.all(
postDataArray.map((e) => {
const isBookmarked = myBookmarklist.includes(e.id)
return postListTemplate(e, isBookmarked)
})
)
//관심공고나 필터링한 데이터 렌더를 위한 항목 비우기
clearContents(tbody)
//데이터 뿌리기
insertLast(tbody, postHtml.join(''))
}
// 공고 리스트 템플렛 생성
//
export function postListTemplate(options, isBookmarked = false) {
const { id, company, position, experience, location, type, stack, companyscore } = options
let experienceText = `${experience[0]}~${experience[1]}년`
if (experience[0] === 0) {
experienceText = `신입~${experience[1]}년`
}
if (experience[1] === 100) {
experienceText = `${experience[0]}년 이상`
}
let template = `
<tr data-id="${id}">
<td>${company}</td>
<td>${position}</td>
<td>${experienceText}</td>
<td>${location}</td>
<td>${type}</td>
<td>${stack}</td>
<td>${companyscore}</td>
<td>
<div class="main-table-btn-wrapper">
<button class="main-table-apply-btn">지원하기</button>
<button class="main-table-bookmark-btn ${isBookmarked ? 'activate' : ''}">${isBookmarked ? '내 관심 공고' : '관심목록 +'}</button>
</div>
</td>
</tr>
`
return template
}
// 왜 Promise.all을 사용해야 할까?
let postHtml = await Promise.all(
postDataArray.map((e) => {
const isBookmarked = myBookmarklist.includes(e.id)
return postListTemplate(e, isBookmarked)
})
)
위 코드의 postRender 함수에서는 렌더링할 HTML 생성을 Promise.all을 이용해 map으로 처리하고 있다. 그런데 이상하다. postListTemplate은 async function이지만, await 구문은 사용하고 있지 않다.
만약 postListTemplate가 async 함수가 아니었다면?
export async function postRender(postDataArray, username = getUserSessionStorage()) {
let myBookmarklist = []
if (username) {
const response = await getMyBookmarks(username)
const bookmarkData = await response.json()
myBookmarklist = bookmarkData.bookmarklist
}
// 렌더할 노드 선택
const tbody = getNode('tbody')
const postHtml = postDataArray.map((e) => {
const isBookmarked = myBookmarklist.includes(e.id)
return postListTemplate(e, isBookmarked)
})
//관심공고나 필터링한 데이터 렌더를 위한 항목 비우기
clearContents(tbody)
//데이터 뿌리기
insertLast(tbody, postHtml.join(''))
}
훨씬 단순한 구조로 map을 사용할 수 있다. 위에서 설명한 argument로 들어간 await으로 처리된 값은 '값'으로 파라미터에 들어가기 때문에 Promise를 이용하지 않아도 된다.
'JS' 카테고리의 다른 글
| [JS] 클로저와 제네레이터 함수 비교 - Closer, Generator Function (0) | 2025.06.30 |
|---|---|
| [JS] 이벤트 최적화, 디바운스와 스로틀 (0) | 2025.06.13 |
| [JS] 실행 컨텍스트 (2) | 2025.06.08 |
| [JS] for in, for of 기능과 예시 (0) | 2025.06.03 |
| [JS] ?. 옵셔널 체이닝(Optional chaining) (0) | 2025.06.02 |