본문으로 건너뛰기
개발 머꼬
개발 노트JavaScript
hohyeon.dev16

reduce로 그룹핑하던 코드와 new Promise 밖으로 resolve를 꺼내던 패턴 정리

  • #Engineering Note
  • #JavaScript

문제 발생

목록을 카테고리별로 묶는 코드는 늘 reduce로 시작했습니다. 짧지도 않고, 초기값과 누산기 갱신에서 한 번씩 실수가 납니다.

const byType = items.reduce((acc, item) => {
  (acc[item.type] ||= []).push(item);
  return acc;
}, {});

그리고 콜백 기반 API를 프로미스로 감쌀 때는 resolve를 바깥으로 꺼내려고 이런 모양을 썼습니다.

let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
socket.on("message", (m) => resolve(m));
socket.on("error", (e) => reject(e));

let 두 개가 초기화되기 전에 참조될 수 있는 것처럼 보이고, TypeScript에서는 타입 단언을 붙여야 통과했습니다.

원인 분석

두 패턴 모두 "표준에 없어서 매번 손으로 쓰는 관용구"였습니다. 사람마다 조금씩 다르게 쓰고, 다르게 쓰는 만큼 리뷰에서 읽는 비용이 듭니다. 최근 몇 해 사이 둘 다 표준 API가 생겼습니다.

  • Object.groupBy(items, callback) — 콜백이 돌려준 문자열을 키로 삼아 묶습니다. 2024년 3월부터 Baseline입니다.
  • Promise.withResolvers()promise, resolve, reject를 한 번에 돌려줍니다. 위의 let 두 개짜리 관용구를 그대로 대체합니다.

해결 방안

  1. 그룹핑은 Object.groupBy로 씁니다.
const byType = Object.groupBy(items, (item) => item.type);
// { vegetables: [...], fruit: [...] }
  1. 반환값이 null 프로토타입 객체라는 점을 압니다. 이건 실수가 아니라 의도입니다 — 데이터에서 온 키가 toString이나 constructor 같은 이름이어도 프로토타입 오염이 일어나지 않습니다. 다만 result.hasOwnProperty(...)처럼 프로토타입 메서드를 기대하는 코드는 실패하므로 Object.hasOwn(result, key)를 씁니다.
  2. 키가 문자열이 아니면 Map.groupBy를 씁니다. Object.groupBy의 키는 문자열이나 심볼이어야 하고 나머지는 문자열로 강제 변환됩니다. 날짜 객체나 다른 객체로 묶어야 한다면 Map.groupBy가 임의의 값을 키로 받습니다.
const byDate = Map.groupBy(logs, (log) => startOfDay(log.at));  // Date를 키로
  1. 콜백 API를 감쌀 때는 Promise.withResolvers()를 씁니다.
const { promise, resolve, reject } = Promise.withResolvers();
socket.on("message", resolve);
socket.on("error", reject);
await promise;
  1. 지원 범위를 확인하고 폴리필을 정합니다. 둘 다 비교적 최근 표준이라, 구형 브라우저나 오래된 Node를 지원해야 한다면 core-js 같은 폴리필이 필요합니다. 지원 대상이 최신 브라우저뿐이라면 그대로 씁니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.