ES2024-2025 주요 변경사항
JavaScript는 매년 ECMAScript 명세를 통해 새로운 기능이 추가됩니다. ES2024와 ES2025에는 개발 생산성을 크게 높이는 기능들이 포함되었습니다. 이 글에서는 실무에서 바로 사용할 수 있는 주요 기능들을 예제와 함께 정리합니다.
Object.groupBy / Map.groupBy (ES2024)
배열의 요소를 특정 기준으로 그룹화하는 정적 메서드입니다. lodash의 groupBy를 네이티브로 사용할 수 있게 되었습니다.
const products = [
{ name: "맥북 프로", category: "노트북", price: 2500000 },
{ name: "아이패드", category: "태블릿", price: 1200000 },
{ name: "갤럭시북", category: "노트북", price: 1800000 },
{ name: "갤럭시탭", category: "태블릿", price: 800000 },
{ name: "LG 그램", category: "노트북", price: 1600000 },
];
// Object.groupBy — 일반 객체로 그룹화
const byCategory = Object.groupBy(products, (p) => p.category);
console.log(byCategory);
// {
// "노트북": [
// { name: "맥북 프로", category: "노트북", price: 2500000 },
// { name: "갤럭시북", category: "노트북", price: 1800000 },
// { name: "LG 그램", category: "노트북", price: 1600000 },
// ],
// "태블릿": [
// { name: "아이패드", category: "태블릿", price: 1200000 },
// { name: "갤럭시탭", category: "태블릿", price: 800000 },
// ]
// }
// 가격대별 그룹화
const byPriceRange = Object.groupBy(products, (p) => {
if (p.price >= 2000000) return "프리미엄";
if (p.price >= 1000000) return "중급";
return "보급형";
});
console.log(Object.keys(byPriceRange));
// ["프리미엄", "중급", "보급형"]
// Map.groupBy — Map 객체로 그룹화 (키에 객체 사용 가능)
const byMap = Map.groupBy(products, (p) => p.category);
console.log(byMap.get("노트북").length); // 3
| 메서드 | 반환 타입 | 키 타입 | 용도 |
|---|---|---|---|
Object.groupBy | 일반 객체 | 문자열/심볼 | 단순 그룹화 |
Map.groupBy | Map | 모든 타입 | 객체를 키로 사용할 때 |
Promise.withResolvers (ES2024)
Promise 생성과 동시에 resolve, reject 함수를 외부로 꺼내는 유틸리티입니다. 콜백 패턴에서 Promise로 전환할 때 특히 유용합니다.
// 기존 방식: resolve/reject를 외부에서 사용하려면 변수 선언 필요
let externalResolve;
let externalReject;
const oldPromise = new Promise((resolve, reject) => {
externalResolve = resolve;
externalReject = reject;
});
// ES2024 방식: 한 줄로 해결
const { promise, resolve, reject } = Promise.withResolvers();
// 실전 예시: 이벤트 기반 코드를 Promise로 변환
function waitForEvent(element, eventName, timeoutMs = 5000) {
const { promise, resolve, reject } = Promise.withResolvers();
const timer = setTimeout(() => {
reject(new Error(`${eventName} 이벤트 ${timeoutMs}ms 타임아웃`));
}, timeoutMs);
element.addEventListener(eventName, (event) => {
clearTimeout(timer);
resolve(event);
}, { once: true });
return promise;
}
// 사용 예시 (브라우저 환경)
// const event = await waitForEvent(button, "click", 3000);
// 실전 예시: 큐 기반 작업 처리
class TaskQueue {
#queue = [];
enqueue(task) {
const { promise, resolve, reject } = Promise.withResolvers();
this.#queue.push({ task, resolve, reject });
this.#process();
return promise;
}
async #process() {
while (this.#queue.length > 0) {
const { task, resolve, reject } = this.#queue.shift();
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
}
}
}
}
Array.fromAsync (ES2024)
비동기 이터러블에서 배열을 생성합니다. Array.from의 비동기 버전입니다.
// 비동기 제너레이터
async function* fetchPages(totalPages) {
for (let page = 1; page <= totalPages; page++) {
// API 호출 시뮬레이션
await new Promise((r) => setTimeout(r, 100));
yield { page, items: [`항목_${page}_1`, `항목_${page}_2`] };
}
}
// 비동기 이터러블을 배열로 변환
const pages = await Array.fromAsync(fetchPages(3));
console.log(pages);
// [
// { page: 1, items: ["항목_1_1", "항목_1_2"] },
// { page: 2, items: ["항목_2_1", "항목_2_2"] },
// { page: 3, items: ["항목_3_1", "항목_3_2"] },
// ]
// 매핑 함수와 함께 사용
const pageNumbers = await Array.fromAsync(
fetchPages(3),
(data) => data.page
);
console.log(pageNumbers); // [1, 2, 3]
Set 메서드 (ES2025)
집합 연산 메서드가 추가되어 교집합, 합집합, 차집합 등을 간편하게 수행할 수 있습니다.
const frontendSkills = new Set(["JavaScript", "React", "CSS", "TypeScript"]);
const backendSkills = new Set(["Node.js", "Python", "TypeScript", "SQL"]);
// 교집합 — 두 집합에 모두 포함된 요소
const common = frontendSkills.intersection(backendSkills);
console.log([...common]); // ["TypeScript"]
// 합집합 — 두 집합의 모든 요소
const allSkills = frontendSkills.union(backendSkills);
console.log([...allSkills]);
// ["JavaScript", "React", "CSS", "TypeScript", "Node.js", "Python", "SQL"]
// 차집합 — 첫 번째 집합에만 있는 요소
const onlyFrontend = frontendSkills.difference(backendSkills);
console.log([...onlyFrontend]); // ["JavaScript", "React", "CSS"]
// 대칭 차집합 — 한쪽에만 있는 요소
const exclusive = frontendSkills.symmetricDifference(backendSkills);
console.log([...exclusive]);
// ["JavaScript", "React", "CSS", "Node.js", "Python", "SQL"]
// 부분집합 검사
const coreSkills = new Set(["JavaScript", "TypeScript"]);
console.log(coreSkills.isSubsetOf(frontendSkills)); // true
console.log(frontendSkills.isSupersetOf(coreSkills)); // true
console.log(frontendSkills.isDisjointFrom(backendSkills)); // false
| 메서드 | 설명 | 수학 기호 |
|---|---|---|
intersection | 교집합 | A ∩ B |
union | 합집합 | A ∪ B |
difference | 차집합 | A - B |
symmetricDifference | 대칭 차집합 | A △ B |
isSubsetOf | 부분집합 여부 | A ⊆ B |
isSupersetOf | 상위집합 여부 | A ⊇ B |
isDisjointFrom | 서로소 여부 | A ∩ B = ∅ |
Iterator.prototype 헬퍼 (ES2025)
이터레이터에 map, filter, take 등의 지연(lazy) 메서드가 추가되었습니다. 배열 메서드와 달리 중간 배열을 생성하지 않아 대용량 데이터 처리에 효율적입니다.
// 대용량 데이터에서 조건에 맞는 처음 5개만 추출
function* naturalNumbers() {
let n = 1;
while (true) {
yield n++;
}
}
// 무한 이터레이터에서 짝수만 골라 처음 5개 추출
const firstFiveEvens = naturalNumbers()
.filter((n) => n % 2 === 0) // 지연 필터링
.map((n) => n * n) // 지연 매핑
.take(5) // 처음 5개
.toArray(); // 배열로 변환
console.log(firstFiveEvens); // [4, 16, 36, 64, 100]
// .drop()으로 앞부분 건너뛰기
const result = naturalNumbers()
.filter((n) => n % 3 === 0)
.drop(2) // 처음 2개 건너뛰기 (3, 6 건너뜀)
.take(3) // 그 다음 3개 (9, 12, 15)
.toArray();
console.log(result); // [9, 12, 15]
배열의 map/filter는 즉시 실행되어 중간 배열을 생성하지만, Iterator 헬퍼는 지연 평가되어 toArray() 호출 시점에 실제 연산이 수행됩니다. 무한 시퀀스도 안전하게 처리할 수 있습니다.
정리
ES2024-2025에 추가된 주요 기능들은 실무에서 자주 사용되는 패턴을 네이티브로 지원합니다.
- Object.groupBy: 배열 요소를 기준별로 그룹화 (lodash 불필요)
- Promise.withResolvers: Promise의 resolve/reject를 외부로 분리
- Array.fromAsync: 비동기 이터러블을 배열로 변환
- Set 메서드: 교집합, 합집합, 차집합 등 집합 연산
- Iterator 헬퍼: map, filter, take 등 지연 평가 메서드
- 브라우저 호환성: 주요 브라우저는 대부분 지원하지만,
Iterator헬퍼는 폴리필이 필요할 수 있습니다