Promise.all([ ])
배열 안의 Promise를 동시에 브라우저에 요청(JS 자체 병렬처리 아님!)하고, 전부 완료되면 결과를 배열로 반환
// 배열 안에 요청할 것 자유롭게 넣을 수 있음
Promise.all([
fetch("/api/users"), // fetch
new Promise(resolve => ...), // 직접 만든 Promise
someAsync(), // async 함수 호출
Promise.resolve({ value: [] }), // 즉시 이행된 Promise
axios.get("/api/data"), // axios 등 라이브러리
42, // 그냥 숫자
"hello" // 그냥 문자열
])
// 그냥 숫자 또는 문자열은 Promise.resolve(42), Promise.resolve("hello") 로 자동 처리됨
유의점
* 순수 작업: web Worker로 병렬처리
실전예제1
1) 안의 함수 동시 요청/ 실행하기
async loadReviews(placeId) {
this._placeId = placeId;
this._page = 1;
this._clearPanel();
await Promise.all([
this._fetchReviews(),
this._fetchStats()
]);
}
2) .map()으로 계산된 결과(배열)을 입력해서 요청하기
* .map() 배열 메서드: 배열내 요소를 연산해서 새 배열 반환
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
// doubled = [2, 4, 6]
_resolveImageUrls: async function (aImages, jwt) {
return Promise.all(aImages.map(async (img, i) => { // .map() 안 콜백 async여야 await 사용 가능
if (img.isExisting) {
return { ID: img.ID, imgurl: img.imgurl, sort_order: i };
}
const formData = new FormData();
formData.append("file", img.file);
const res = await fetch(`${BASE}/uploadImage`, {
method: "POST",
headers: { "Authorization": `Bearer ${jwt}` },
body: formData
});
if (!res.ok) throw new Error(this._i18n.getText("reviewImageFailMsg"));
const { url } = await res.json();
return { imgurl: url, sort_order: i };
}));
},
Promise.resolve(값)
처음부터 성공 상태인 Promise를 즉시 반환. 로직 없이 값을 Promise형태로 포장해서 출력할 때 사용.
Promise.resolve(42)
Promise.resolve({ value: [] })
Promise.resolve("완료")
유의점
Promise.reject(에러)
처음부터 실패 상태인 Promise를 즉시 반환. 로직 없이 의도적으로 에러를 던질 때 사용.
await Promise.reject(new Error("실패")); // await가 throw처럼 동작
Promise.reject(new Error("권한 없음"))
Promise.reject("잘못된 요청")
유의점
Promise.reject(new Error("실패"))
.catch(err => console.log(err));
async function fn() {
try {
await Promise.reject(new Error("실패")); // 여기서 던짐
} catch (err) {
console.log(err); // 여기서 잡음
}
}
// ❌ 잘못된 사용
async function fn() {
await Promise.reject(new Error("실패"));
console.log("여기는 실행 안 됨");
}
fn(); // UnhandledPromiseRejection 경고 발생
즉, Promise 뒤에 .resolve() 나 .reject() 가 나오면 () 안의 내용물을 Promise형식의 결과로 반환하는 것
실전 예제2
// ── 내부: fetch ────────────────────────────────────────
async _fetchAllBookmarks() {
this._controller.getView().setBusy(true);
const jwt = sessionStorage.getItem("jwt");
const userId = sessionStorage.getItem("userId");
try {
// 북마크 + 내 리뷰 목록 동시 조회 요청
const [bmRes, rvRes] = await Promise.all([
fetch(
`/jmt/PersonalMarkers`
+ `?$filter=user_ID eq '${userId}'`
+ `&$expand=restaurant($select=ID,place_name,category_name,`
+ `address_name,road_address_name,latitude,longitude,phone,place_url)`
+ `&$orderby=createdAt desc`,
{ headers: { "Authorization": `Bearer ${jwt}` } }
),
fetch(
`/jmt/Reviews?$filter=createdBy eq '${userId}'&$select=restaurant_ID`,
{ headers: { "Authorization": `Bearer ${jwt}` } }
)
]);
if (!bmRes.ok) throw new Error(await bmRes.text());
const [bmData, rvData] = await Promise.all([
bmRes.json(),
rvRes.ok ? rvRes.json() : Promise.resolve({ value: [] })
]);
const reviewedIds = new Set(rvData.value.map(r => r.restaurant_ID));
this._allBookmarks = bmData.value.map(b => {
const { id, x, y, ...r } = restaurant.buildPlace(b.restaurant || {});
const categoryClean = r.category_name.replace(/^음식점\s*>\s*/, "");
const categoryFirst = categoryClean.split(">")[0].trim();
return {
_markerId: b.ID,
restaurant_ID: r.ID,
...r,
categoryDisplay: categoryClean, // 리스트 표시용 (음식점> 제거 후 전체 경로)
categoryFirst, // 필터 판단용
displayAddress: r.road_address_name || r.address_name || "",
memo: b.memo || null,
createdAt: b.createdAt,
createdAtFormatted: date.formatDate(b.createdAt),
hasMyReview: reviewedIds.has(r.ID)
};
});
this._applyFiltersAndRender();
} catch (e) {
console.error("북마크 조회 실패", e);
} finally {
this._controller.getView().setBusy(false);
}
}
원본은 참 긴데 Promise를 중점으로 하나하나 해석해보자
const [bmRes, rvRes] = await Promise.all([
fetch(
`/jmt/PersonalMarkers`
+ `?$filter=user_ID eq '${userId}'`
+ `&$expand=restaurant($select=ID,place_name,category_name,`
+ `address_name,road_address_name,latitude,longitude,phone,place_url)`
+ `&$orderby=createdAt desc`,
{ headers: { "Authorization": `Bearer ${jwt}` } }
),
fetch(
`/jmt/Reviews?$filter=createdBy eq '${userId}'&$select=restaurant_ID`,
{ headers: { "Authorization": `Bearer ${jwt}` } }
)
]);
fetch() : ()안의 주소로 브라우저 요청
-> 두 fetch를 동시에 요청한다.
const [첫번째, 두번째] = await Promise.all([fetch1, fetch2]);
// 첫번째 === fetch1 결과
// 두번째 === fetch2 결과
-> 각 fetch를 bmRes, rvRes에 저장
이 바로 뒤에 .text()는 서버가 에러 응답을 body로 보내므로 이 body를 텍스트로 읽는 함수
* fetch는 에러를 내도 reject하지않으므로 수동으로 잡아야함 -> fetch담은변수.ok 확인 필요
if (!bmRes.ok) throw new Error(await bmRes.text());
그런데, 이 구조를 보니
const [bmData, rvData] = await Promise.all([
bmRes.json(),
rvRes.ok ? rvRes.json() : Promise.resolve({ value: [] })
]);
이걸보니 bmRes은 throw, rvRes는 Promise.all안에서 처리해서 방식이 일관되지 않음
if (!bmRes.ok) throw new Error(await bmRes.text());
const bmData = await bmRes.json();
const rvData = rvRes.ok ? await rvRes.json() : { value: [] };
그런데 또 자세히보니 .ok로 검증하는 방식은 여기저기서 여러번 반복해서 사용하는 로직인데 util/validation.js로 빼야겠는데?
/**
* fetch 응답을 검증하고 필요시 JSON으로 파싱해서 반환합니다.
*
* @param {Response} res - fetch()가 반환한 Response 객체
* @param {string} [errorMessage] - 커스텀 에러 메시지. 없으면 서버 메시지 사용
* @param {boolean} [parseJson=true] - false면 검증만 하고 파싱 안 함
* @returns {Promise<any|void>} parseJson이 true면 파싱된 JSON, false면 undefined
* @throws {Error} HTTP 에러 응답일 경우 에러
*
* @example
* // 기본 사용 (서버 메시지 + JSON 파싱)
* const data = await validation.parseResponse(res);
*
* // 커스텀 에러 메시지 + JSON 파싱
* const url = await validation.parseResponse(res, this._i18n.getText("reviewImageFailMsg"));
*
* // 검증만 (JSON 파싱 없이)
* await validation.parseResponse(res, null, false);
*/
parseResponse: async function (res, errorMessage, parseJson = true) {
if (!res.ok) {
throw new Error(errorMessage ?? await res.text());
}
if (parseJson) return res.json();
},
이렇게 util 에 넣어놓고
const bmData = await validation.parseResponse(bmRes);
const rvData = rvRes.ok ? await rvRes.json() : { value: [] };
이렇게 바꾸면 됨 (2번째 Promise가 굳이 필요없는듯?)
const reviewedIds = new Set(rvData.value.map(r => r.restaurant_ID));
// ...
return {
// ...
hasMyReview: reviewedIds.has(r.ID)
};
Set로 쓰면 .has()가 배열의 .includes()보다 빠른 연산처리: 리뷰데이터 목록에서 빠른 조회
this._allBookmarks = bmData.value.map(b => { ... });
북마크 목록 데이터(b)를 중첩구조를 없애고 납작하게 펼치기