반응형

“What's my story?” & “My Parallel Universe Job” 아바타 세트 아이디어 ✨

요즘 AI 이미지 생성으로 재미있는 프로필 이미지를 만드는 게 유행이죠?
하지만 단순히 지브리 스타일이나 픽사풍 이미지 말고, 더 창의적이고 특별한 방식으로 자신을 표현할 수는 없을까요?

오늘은 **내 취미와 상상력을 담은 ‘프리미엄 아바타 수집 세트’**라는 콘셉트로, 두 가지 기발한 프롬프트를 소개합니다.
이 아이디어들은 당신만의 개성을 담은 프로필 이미지나, 블로그・SNS 아바타, 혹은 자기소개 콘텐츠로 활용하면 어떨까요!

1️⃣ “What's my story?” 아바타 세트

Create a premium wooden box-style collectible avatar display titled “What’s my story?” for a 45-year-old.
The central figure is a stylized cartoon portrait of the person in the photo, slightly smiling and full of character. The figure is surrounded by a collection of highly detailed miniature items placed in labeled compartments:

  • a tiny notebook
  • a retro handheld game console
  • a classic robot figurine
  • wireless headphones
  • running shoes
  • a folded bicycle
  • a compact fishing rod & colorful lures
  • a mug of black coffee
  • a desk plant
  • a miniature open laptop with code on screen

All items should look realistic but with a playful, collectible figure aesthetic (like Nendoroid or Bearbrick style).
The wooden box background should be warm and grainy, with engraved text:

Top: "What’s my story?"
Bottom: "Age: 45 | Hobbyist. Dreamer. Explorer."

The overall vibe is cozy, creative, and slightly nostalgic — perfect for a modern avatar with personality and charm.

이 프롬프트는 마치 당신의 삶을 장난감 상자에 담아낸 듯한 느낌을 줍니다.
실제 자신이 좋아하는 아이템들을 미니어처로 구성하면서, 그 자체가 자기소개가 되는 거죠.


2️⃣ “My Parallel Universe Job” 아바타 세트

Create a wooden-box style premium collectible avatar display titled "My Parallel Universe Job" for a 45-year-old.
The central figure is a stylized cartoon avatar of the person in the photo, reimagined as a “Time-Traveling Librarian” — wearing steampunk-inspired gear, with brass goggles, a book satchel, and a glowing compass.

Surround the avatar with labeled compartments, each holding magical or sci-fi themed mini items:

  • An enchanted floating book
  • A mini time compass
  • A scroll with glowing runes
  • A tiny steampunk coffee machine
  • A magical plant that reacts to sound
  • A pocket-sized AI assistant in a crystal orb
  • Mini shelves of otherworldly books
  • A holographic cat companion
  • A star map

The wooden box is dark walnut with celestial engravings.

Top label: "My Parallel Universe Job"
Bottom label: "Time-Traveling Librarian | Age 45 | Keeper of Stories & Time"

The entire display should have a mystical, cozy, and geeky charm — like a collectible from a fantasy convention.

이건 정말 상상력을 자극하는 프롬프트입니다.
평행세계에서의 나의 직업을 테마로 설정해, 마치 영화 속 캐릭터처럼 나를 새롭게 재해석할 수 있죠.
'시간 여행하는 사서' 말고도 우주 요리사, 몬스터 유치원 선생님, 드림 엔지니어 등으로도 확장할 수 있어요.


🪄 마무리 팁

이 프롬프트들은 DALL·E, Midjourney, Leonardo AI 같은 이미지 생성 AI에 넣으면 꽤 괜찮은 결과물이 나옵니다.
조금만 편집해서 SNS 프로필, 명함, 웹사이트, 소개 슬라이드 등에 넣어보세요.
지브리풍도 좋지만 그와 다른 기억에 남을 캐릭터를 만들 수 있을 거라 생각합니다.

728x90
반응형
반응형

Node.js 개발에서 TypeScript를 도입할 때 가장 먼저 마주치는 고민 중 하나는 바로 이것입니다.

TypeScript를 전역으로 설치할까? 로컬로 설치할까?

이 글에서는 두 방식의 차이점, 장단점, 실제 추천 시나리오까지 한번에 정리해드립니다.


✅ 전역 설치란?

전역 설치는 시스템 전체에 TypeScript를 설치하여 어느 프로젝트 디렉토리에서도 tsc 명령어를 사용할 수 있게 하는 방식입니다. 다음과 같은 명령어로 설치합니다:

npm install -g typescript

설치 후에는 터미널 어디에서나 다음 명령이 작동합니다:

tsc --version
tsc yourfile.ts

전역 설치의 장점

  • tsc 명령을 터미널 어디서든 바로 쓸 수 있다
  • CLI 툴 사용 시 빠르게 실행 가능
  • 전역 설정에 익숙한 개발자에게 편리

전역 설치의 단점

  • 버전 충돌 가능성: 프로젝트마다 TypeScript 버전이 다를 경우 문제 발생
  • 팀 협업에서 위험: 다른 개발자의 시스템에 같은 버전이 설치되어 있다고 보장할 수 없음
  • CI/CD 파이프라인에서 재현 불가

✅ 로컬 설치란?

로컬 설치는 해당 프로젝트 폴더 안에 TypeScript를 설치하는 방식입니다. 보통 개발 의존성으로 추가합니다:

npm install --save-dev typescript

설치 후에는 전역 명령 대신 npx를 사용해 실행합니다:

npx tsc
npx tsc --init

로컬 설치의 장점

  • 프로젝트별로 TypeScript 버전을 고정할 수 있어 안정성 확보
  • 모든 팀원이 동일한 환경에서 작업 가능
  • CI/CD 환경에서도 100% 재현 가능
  • package.json만 보면 어떤 버전이 쓰였는지 알 수 있다

로컬 설치의 단점

  • tsc를 직접 실행하려면 npx 또는 경로를 지정해야 한다
  • 여러 프로젝트를 오가며 전역 설치처럼 쓰고 싶으면 약간 번거로움

🔍 어떤 상황에 어떤 설치를 써야 할까?

  • 혼자 실습하거나 빠르게 테스트하는 경우
    전역 설치도 무방합니다.
  • 팀 프로젝트, 버전 관리, 협업, CI/CD 구성이 필요한 경우
    반드시 로컬 설치가 권장됩니다.

요즘 대부분의 실제 프로젝트는 typescript를 로컬로 설치하고 npx tsc 또는 tsc npm script로 실행합니다.


🧙 마무리

전역 설치는 빠르고 편리하지만, 협업과 재현성을 위해서는 로컬 설치가 훨씬 안전하고 추천되는 방식입니다.

최종적으로는 다음과 같은 전략을 추천합니다:

  • CLI 툴 실습이나 글로벌 테스트용 → 전역 설치
  • 실제 서비스, 팀 프로젝트, 오픈소스, CI/CD → 로컬 설치 (with --save-dev)

🎯 참고 명령어 요약

# 전역 설치
npm install -g typescript

# 로컬 설치
npm install --save-dev typescript

# 로컬 실행
npx tsc

 

728x90
반응형
반응형

🔍 ??란?

??는 null 병합 연산자 (Nullish Coalescing Operator) 입니다.
JavaScript(ES2020)부터 도입된 문법으로, 값이 null 또는 undefined일 때 대체값(default value) 을 설정하는 데 사용됩니다.

const result = value ?? defaultValue;

📘 동작 방식

왼쪽 값 ?? 결과
null 오른쪽 값 반환
undefined 오른쪽 값 반환
나머지 값들 (0, '', false) 왼쪽 값 그대로 사용
let name = null ?? "익명";       // "익명"
let age = undefined ?? 30;       // 30
let isActive = false ?? true;    // false ← 주의!
let count = 0 ?? 10;             // 0 ← 주의!

✅ false, 0, "" 는 null이 아님! → ??는 무시하고 그대로 반환


🔁 || (OR 연산자)와 차이점?

||는 falsy 값 전체를 체크

let title = "" || "기본 제목";  // "기본 제목"

??는 null / undefined 만 체크

let title = "" ?? "기본 제목";  // ""​
표현식 결과 이유
`null   '기본'`
`0   100`
0 ?? 100 0 null/undefined 아니므로 유지

결론:

  • ||는 많이 비우고 싶을 때
  • ??는 정말 null/undefined일 때만 대체하고 싶을 때 사용하세요.

🧱 실전 예시

✅ 환경 변수 기본값 설정

const PORT = process.env.PORT ?? 3000;

→ PORT가 설정되지 않았을 때만 기본값 3000 사용


✅ 사용자 입력 처리

function getUserName(user) {
  return user.name ?? "손님";
}

→ user.name이 null 또는 undefined일 때만 "손님" 반환


✅ 객체 병합

const defaultOptions = {
  retry: 3,
  timeout: 5000
};

const userOptions = {
  timeout: null
};

const config = {
  retry: userOptions.retry ?? defaultOptions.retry,
  timeout: userOptions.timeout ?? defaultOptions.timeout
};

console.log(config); // { retry: 3, timeout: 5000 }

→ timeout: null은 무시하고 default 사용


❌ 에러 주의: 옵셔널 체이닝과 혼용 시

let username = user?.info?.name ?? "익명";
  • user 또는 info가 없으면 undefined
  • ??로 "익명" 설정됨 → 매우 강력한 조합
728x90
반응형
반응형

작년 11월에 구매했던 에어맥스 플러스 TN 두 모델 

리복 퓨리와 나이키 에어맥스 95, 플러스 TN 모델을 좋아해서 살때 두세켤레 사서 돌려신는 편인데

나이가 들수록 퓨리는 너무 단단한 미드솔때문에 발이 아프더라구요

에어맥스가 쿠션이 단단한듯하지만 과체중을 잘 받쳐주는 느낌이 있어 최근엔 맥스류만 신게 되네요

파리생제르맹 컬러

파리 생제르맹과 콜로보한 제품인데 텅에 있는 생제르맹 엠블럼이 맘에 안들었지만 어퍼컬러가 맘에 들어서 샀습니다

인기가 없어서인지 할인도 많이 해서 10만원 이하로 떨어졌던 것도 한몫 했습니다

정면에서 바라본 어퍼 생상과 흰색의 웨이브 패턴의 조합이 꽤 맘에 듦니다
TN 고유의 패턴입니다

측면의 색은 그라데이션 처리가 되어 있습니다

스우시는 과하지 않은 크기에 금색으로 포인트를 주었네요

에어는 앞축은 95같고 뒤축은 조금 다른 구조로 되어 있어서 아웃솔의 모양이 조금 다릅니다

설포는 고정되어 있지 않습니다. 설포 중간에 끈을 넣어 고정하는 부분도 없어서 신다보면 설포가 바깥쪽으로 심하게 돌아갑니다

나온지도 오래된 제품인데 개선점이 없이 나오는건 많이 아쉽더라고요

저만 그런걸까요?

두번째 모델은 화이트 블랙 볼트 색상 입니다. 에어 내부가 형광색이여서 선택했는데요

스우시도 형광색으로 깔맞춤 되어 있습니다.

생제르맹은 앞에서 뒤로 그라데이션인데 이모델은 위에서 아래로 그라데이션 처리가 되어 있습니다

중간쯤 보면 플라스틱이 있는데 중족부 지지대로 발의 뒤틀림을 방지해 준다고 하네요

발등 부분은 흰색으로 되어 있습니다
설포에 나이키 에어 앰블럼이 들어가 있는데 생제르맹과의 또 다른 차이점 이네요 

플러스 TN제품의 어퍼는 거의 직물인 경우가 많은 생제르맹 제품과 이 제품의 직물이 좀 촘촘하고 단단한 느낌이 있습니다

이게 내구성이 좋을지는 신어봐야 알겠지만 예전에 신었어 맥스 270과 최근까지 신고 있는 유니버시티 레드 색의 TN의 뱀프는 찢어지는 문제가 있었습니다

현재 신고 있는 TN인데 걸을때 접히는 부분의 바깥쪽이 찢어지고 있네요

예전부터 느끼지만 나이키 제품이 은근히 내구성이 약한거 같습니다  

그리고 뒤축이 찢어지는 이슈

지하철에서 종종 밟히다 보니 찢어지는건데 TN이나 95나 둘다 그러네요

 

착화감은 꽤 괜찮은 편입니다

95는 발을 아늑하게 꽉 잡아주는 느낌이라면 TN은 부드럽게 감싸는 느낌이랄까요

사이즈는 반업이 적당했습니다

제가 느끼는 맥스 플러스 TN은 요즘 나오는 운동화들보다 무겁고 단단한 미드솔의 느낌이 있지만 반발력과 지지력이 나쁘지 않아 일상화로 괜찮지 않나 생각합니다

728x90
반응형
반응형

5~6개월 전인가 한창 리뷰가 나오던 다이소 아크릴 마커펜을 한달 전에 구매하게 되었네요.

동네 매장에는 품절이었고 다이소 공식몰에서 품절이라고 했는데 방문 수령으로 하니 강남 본점에 소량 재고가 있더라고요.

재고가 있는데 온라인 주문은 안되고 방문 수령만 되는건 왜 일까요?

아무튼 두개 주문해서 받아왔는데 최근에 다른 매장에 가보니 재입고 되었는지 재고량이 여유있어 보였습니다.

3천원에 무려 12가지 색이 들어있습니다.

건담 도색을 하지 않으면 사실상 그리 와닿지 않을거 같은 말이죠

저는 가조립위주로 건프라를 즐기는 사람이라 이 제품이 이슈가 됐을때도 감흥이 없었는데요

붓도색을 시도해보고 마커펜도 사볼까란 생각이 들어 건프라 마커펜을 찾아보니 다이소 제품이 왜 인기가 있는지 알겠더라고요.

펜의 아래와 손가락 파지하는 부분의 색이 표현되어 있어 추측을 할 수는 있지만 12가지 색이 어떤색인지는 써있지가 않네요.

이것은 건담마커 어드밴스드 세트인데요 무려 19,500원 입니다. 최저가로 찾아보면 더 저렴한 제품들이 있겠지만 저는 하비팩토리를 애용하기에 딱히 찾아보진 않았네요.

 

하비팩토리

하비팩토리 건담 프라모델 피규어의 강자 하비팩토리

www.hobbyfactory.kr

3000원 vs 19,500원 

가격만 보면 다이소 제품이 정말 말도 안되는 가격에 나왔다는걸 알 수 있습니다.

건담 마커펜은 펜촉을 눌러서 아크릴 도료가 나오면 도색하는 형태이고 다이소 제품은 일반 펜처럼 바로 사용 할 수가 있습니다.

펜촉의 형태도 확연히 다르게 생겼습니다.

위에는 다이소 마커 아래는 건담 마커를 사용해서 종이에 선을 그어서 비교해보았습니다.

다이소 마커펜도 옆으로 많이 눕히면 넓게 도색이 될거 같긴하지만 그래도 넓은면 도색은 마커펜이 조금 더 좋을 듯 하네요.

다이소 마커펜을 이용해서 도색을 해보았습니다. 

어두운 회색으로 갑옷의 일부분을 칠해봤는데 발림성이 굉장히 좋았습니다.

발색도 맘에 들었습니다.

표면서페이서를 바르거나 사포로 문지르지 않아도 색 표현력이 충분히 좋았습니다.

물론 저는 도색에 진심이 아니다 보니 그럴수도 있겠지만 재미삼아 사용하기엔 충분하지 않나 싶네요. 

기존에 스티커가 붙어있던걸 떼어내고 칠해보았습니다.

면적이 조금 더 넓어지니 덧칠의 필요성이 눈에 보이네요.

마른 뒤 덧칠을 해주었습니다. 면적이 넓으면 최소 한번은 덧칠을 해주어야 할거 같습니다.

전체가 빨간색으로 되어 있는 병사 로봇을 칠해 보았습니다.

역시 넓은 면적에는 부분적으로 도색이 미흡한게 눈에 띄네요.

그리고 펜축이 굵다 보니 원치않는 위치에 색칠이 되는 문제가 있습니다. 

그럼에도 불구하고 건프라의 오리지널리티가 중요하지 않다면 충분히 재미있게 도색을 해볼 수 있지 않을까 생각되네요.

 

728x90
반응형
반응형

기본적으로 new Date()는 UTC 기준으로 동작합니다. 이를 **KST(한국 표준시, UTC+9)**로 변환하는 방법을 설명합니다.

🚀 1. Intl.DateTimeFormat 사용 (권장)

JavaScript의 Intl.DateTimeFormat API를 사용하면 타임존 변환이 간단합니다.

const date = new Date();

const kstDate = new Intl.DateTimeFormat('ko-KR', {
  timeZone: 'Asia/Seoul',  // ✅ KST 적용
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
  hour12: false, // ✅ 24시간제 사용
}).format(date);

console.log(`📌 KST 시간: ${kstDate}`);

 

💡 출력 예시:

📌 KST 시간: 2024. 02. 25. 15:30:45

Intl.DateTimeFormat을 사용하는 이유

  • 최신 표준 지원 (ECMAScript 국제화)
  • 시간대 자동 적용
  • 성능 최적화

 

🚀 2. toLocaleString() 사용

toLocaleString()을 사용하여 KST로 변환할 수도 있습니다.

const date = new Date();
const kstDate = date.toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' });

console.log(`📌 KST 시간: ${kstDate}`);

💡 출력 예시:

📌 KST 시간: 2024. 2. 25. 오후 3:30:45

✅ toLocaleString()은 간편하지만, 시간 형식이 다소 달라질 수 있음.

 

 

🚀 3. moment-timezone 사용 (강력한 기능 제공)

moment-timezone을 사용하면 더 다양한 포맷 지정 가능.

📌 설치

npm install moment-timezone

📌 코드 예제

const moment = require('moment-timezone');

const kstDate = moment().tz("Asia/Seoul").format("YYYY-MM-DD HH:mm:ss");

console.log(`📌 KST 시간: ${kstDate}`);

💡 출력 예시:

📌 KST 시간: 2024-02-25 15:30:45

moment-timezone 사용 시 장점

  • 여러 타임존 변환 가능
  • 포맷 지정 가능
  • Date 객체와 연동 가능

 

🚀 4. Date 객체에서 직접 변환 (UTC 오프셋 적용)

UTC 시간에 9시간을 추가하여 KST로 변환할 수도 있습니다.

const date = new Date();
const kstDate = new Date(date.getTime() + (9 * 60 * 60 * 1000)); // ✅ UTC+9 적용

console.log(`📌 KST 시간: ${kstDate.toISOString().replace('T', ' ').slice(0, 19)}`);

💡 출력 예시:

📌 KST 시간: 2024-02-25 15:30:45

✅ 직접 Date 객체를 변환하는 방법이지만, Intl.DateTimeFormat보다 추천하지 않음.

 

 

🎯 정리 (어떤 방법을 선택할까?)

방법코드 예제장점단점
Intl.DateTimeFormat ✅ new Intl.DateTimeFormat('ko-KR', { timeZone: 'Asia/Seoul' }) 표준 지원, 성능 최적화 포맷 지정이 다소 제한적
toLocaleString() ✅ date.toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' }) 간편 사용 출력 형식이 다를 수 있음
moment-timezone ✅ moment().tz("Asia/Seoul").format("YYYY-MM-DD HH:mm:ss") 강력한 포맷팅 기능 패키지 설치 필요
UTC 오프셋 변환 ✅ new Date(date.getTime() + (9 * 60 * 60 * 1000)) 직접 변환 가능 DST(일광 절약 시간) 고려 안됨

📌 추천 방법

  • 최신 프로젝트 & 표준 지원이 필요하면 → Intl.DateTimeFormat()
  • 간단하게 변환하려면 → toLocaleString()
  • 강력한 포맷팅이 필요하면 → moment-timezone
  • UTC에서 오프셋 변환하려면 → Date + 9시간 추가
728x90
반응형
반응형

Vue.js를 개발할 때 yarn run dev 또는 npm run dev 명령어를 실행하면, 로컬에서 개발 서버가 실행됩니다. 하지만 실수로 Control + Z (^Z)를 눌러 suspended(정지됨) 상태가 되면, 터미널에서 다시 실행하거나 정상적으로 종료하는 방법이 필요합니다.

이 글에서는 Mac에서 Vue.js 개발 서버가 Suspended 상태가 되었을 때 해결하는 4가지 방법을 소개합니다.


✅ Suspended 상태란?

Suspended(정지됨) 상태란?
터미널에서 yarn run dev 실행 중 Control + Z 키를 눌렀을 때, 실행 중이던 프로세스가 중지(Suspended)되고 백그라운드로 이동하는 상태를 말합니다.

이 상태에서는 서버가 멈춘 것처럼 보이지만, 여전히 프로세스가 살아있습니다. 이를 해결하기 위해 다시 실행하거나 종료하는 방법을 알아보겠습니다.


🔥 해결 방법 1: fg를 사용해 다시 실행 후 종료

1.1 정지된 프로세스를 다시 실행

터미널에서 아래 명령어를 입력하면, 중지된 프로세스를 다시 실행할 수 있습니다.

fg

이후 Control + C (^C)를 눌러 정상 종료합니다.

📝 설명:
fg(foreground) 명령어는 suspended 상태에서 프로세스를 다시 실행합니다.


🔥 해결 방법 2: 백그라운드 실행 후 종료

2.1 정지된 프로세스를 백그라운드에서 실행

bg

이 명령어를 입력하면, 중지된 프로세스가 백그라운드에서 실행됩니다.

2.2 실행 중인 백그라운드 작업 확인

jobs -l

이 명령어를 입력하면, 백그라운드에서 실행 중인 프로세스 목록이 표시됩니다.

[1]  +  12345 Suspended   yarn run dev

위에서 12345는 프로세스 ID(PID)입니다.

2.3 특정 프로세스 종료

kill 12345

또는 아래 명령어를 사용해 백그라운드 프로세스를 종료할 수도 있습니다.

kill %1

📝 설명:
%1은 jobs 명령어에서 [1]로 표시된 백그라운드 작업의 번호입니다.


🔥 해결 방법 3: 강제 종료 (kill -9 사용)

만약 fg 또는 kill로도 프로세스가 종료되지 않는다면, 강제로 종료하는 방법이 있습니다.

3.1 실행 중인 프로세스 ID(PID) 찾기

ps aux | grep yarn

이 명령어를 실행하면 실행 중인 yarn run dev의 프로세스 ID(PID)를 찾을 수 있습니다.

user     12345   0.0  0.1  2458908  8000 s001  S+   10:34AM   0:02.34 node /path/to/yarn run dev

위에서 12345가 프로세스 ID입니다.

3.2 해당 PID를 강제 종료

kill -9 12345

📝 설명:
kill -9는 강제 종료 명령어로, 해당 프로세스를 즉시 중단시킵니다.


🔥 해결 방법 4: 터미널을 닫고 새로 열기

만약 위 방법으로도 해결되지 않는다면, 가장 간단한 방법은 터미널을 닫고 다시 여는 것입니다.

  1. 현재 실행 중인 터미널을 닫습니다.
  2. 새 터미널을 열고 다시 yarn run dev를 실행합니다.

이 방법을 사용하면, 기존에 중지된 프로세스가 자동으로 종료되면서 새로운 프로세스로 실행됩니다.


🚀 최종 정리: 어떤 방법을 선택할까?

해결 방법명령어추천 상황

Suspended 상태에서 다시 실행 후 종료 fg → Control + C 가장 일반적인 해결책
백그라운드에서 실행 후 종료 bg → jobs -l → kill %1 여러 개의 프로세스가 있을 때
프로세스 ID로 강제 종료 `ps aux grep yarn→kill -9 <PID>`
터미널 닫고 재시작 터미널 종료 후 다시 실행 모든 방법이 안될 때

💡 가장 추천하는 방법:

  • fg로 복구한 후 Control + C로 종료하는 것이 가장 빠르고 안정적인 해결책입니다.
  • 하지만 프로세스가 종료되지 않는다면 kill -9 <PID>를 사용하세요.

이제 Vue.js 개발 서버가 suspended 상태에 빠졌을 때 당황하지 말고, 위 방법을 활용하여 빠르게 해결해보세요! 🚀

 

#VueJS #MacOS #TerminalCommands #Debugging #DevTips #FrontendDev #WebDevelopment #JavaScript

728x90
반응형
반응형

fs.createReadStream + csv-parser 사용 시 비동기 처리에 주의하세요!


📌 문제 상황: 비동기 스트림에서 예상치 못한 동작

CSV 파일을 읽고, 특정 수량(quantity)만큼 데이터를 처리하려고 했지만, 모든 데이터가 처리됨. 😲

목표:

  • CSV 데이터에서 특정 개수(quantity)만 읽고 중단
  • 비동기 함수(await)가 정확히 작동하도록 수정

💥 문제 발생 코드: 비동기 흐름 제어 실패

import fs from 'fs';
import csv from 'csv-parser';

let count = 0;
const quantity = 20;
const winDataList: any[] = [];

fs.createReadStream(filePath)
  .pipe(csv())
  .on("data", async (data: CsvRow) => {
    if (count < quantity) {
      const email = data.email;
      const point = data.point;
      const choice = data.choice;
      const choiceImage = { a: "imagea", b: "imageb" }[choice]!;

      winDataList.push({ email, point, choice, choiceImage });
      console.log("Processing:", email, point);

      await updateEmailFile(email, fileName); // 비동기 작업
      await updateEmail(email);              // 비동기 작업

      count++;
    } else {
      return; // ❌ 스트림은 계속 실행됨
    }
  });

⚠️ 문제 원인:

  1. csv-parser는 비동기 흐름을 기다리지 않음.
  2. await 사용했지만, 스트림은 비동기 작업과 무관하게 계속 실행.
  3. 결과: 모든 CSV 데이터가 처리됨. 😱

해결 방법: 스트림 흐름을 명확히 제어

### 🎯 방법 1: stream.destroy()로 스트림 종료

count가 quantity에 도달하면 스트림 자체를 종료하여 추가 데이터를 읽지 않도록 합니다.

import fs from 'fs';
import csv from 'csv-parser';

let count = 0;
const quantity = 20;
const winDataList: any[] = [];

const stream = fs.createReadStream(filePath)
  .pipe(csv());

stream.on("data", async (data: CsvRow) => {
  if (count < quantity) {
    const email = data.email;
    const point = data.point;
    const choice = data.choice;
    const choiceImage = { a: "imagea", b: "imageb" }[choice]!;

    winDataList.push({ email, point, choice, choiceImage });
    console.log("Processing:", email, point);

    await updateEmailFile(email, fileName);
    await updateEmail(email);

    count++;

    if (count >= quantity) {
      stream.destroy(); // ✅ 스트림 완전 종료
    }
  }
});

stream.on("close", () => {
  console.log("Stream closed after processing", count, "items.");
});

stream.on("error", (err) => {
  console.error("Stream error:", err);
});

💡 장점:

  • 간단하고 빠른 구현
  • quantity에 도달하면 즉시 스트림 종료

단점:

  • 스트림 완전 종료: 이후 데이터는 절대 다시 읽을 수 없음

### 🎯 방법 2: pause() & resume()으로 정교한 제어

각 데이터 처리 시 스트림을 일시정지하고, 비동기 작업 완료 후 다시 재개합니다.

import fs from 'fs';
import csv from 'csv-parser';

let count = 0;
const quantity = 20;
const winDataList: any[] = [];

const stream = fs.createReadStream(filePath)
  .pipe(csv());

stream.on("data", async (data: CsvRow) => {
  if (count < quantity) {
    stream.pause(); // ✅ 데이터 흐름 일시 정지

    const email = data.email;
    const point = data.point;
    const choice = data.choice;
    const choiceImage = { a: "imagea", b: "imageb" }[choice]!;

    winDataList.push({ email, point, choice, choiceImage });
    console.log("Processing:", email, point);

    await updateEmailFile(email, fileName);
    await updateEmail(email);

    count++;

    if (count >= quantity) {
      stream.destroy(); // ✅ 최대 수량 도달 시 스트림 종료
    } else {
      stream.resume(); // ✅ 다음 데이터 읽기 재개
    }
  }
});

stream.on("close", () => {
  console.log("Stream closed after processing", count, "items.");
});

stream.on("error", (err) => {
  console.error("Stream error:", err);
});

💡 장점:

  • 비동기 흐름 제어 가능 (데이터 순서 보장)
  • 중간에 작업 재개 가능

단점:

  • 코드 복잡성 증가

🎯 방법 3: pipeline()으로 스트림 관리 최적화

Node.js 10+에서는 **stream.pipeline()**을 사용해 에러 핸들링비동기 흐름 관리를 더 효율적으로 처리할 수 있습니다.

import { createReadStream } from 'fs';
import { pipeline } from 'stream';
import csv from 'csv-parser';
import { promisify } from 'util';

const asyncPipeline = promisify(pipeline);

async function processCSV(filePath: string, quantity: number) {
  let count = 0;
  const winDataList: any[] = [];

  await asyncPipeline(
    createReadStream(filePath),
    csv(),
    async function* (source) {
      for await (const data of source) {
        if (count >= quantity) break;

        const email = data.email;
        const point = data.point;
        const choice = data.choice;
        const choiceImage = { a: "imagea", b: "imageb" }[choice]!;

        winDataList.push({ email, point, choice, choiceImage });
        console.log("Processing:", email, point);

        await updateEmailFile(email, fileName);
        await updateEmail(email);

        count++;
      }
    }
  );

  console.log("Processed", count, "records");
}

processCSV(filePath, 20).catch(console.error);

💡 장점:

  • 에러 핸들링 포함
  • 비동기 흐름 자연스럽게 제어 가능
  • 중간에 멈추거나 재시작 가능

단점:

  • Node.js 10+ 필요
  • 복잡한 로직에는 적합하지 않을 수 있음

결론: 어떤 방법을 선택할까?

방법장점단점추천 상황

stream.destroy() 빠르고 단순 이후 데이터 다시 읽기 불가 간단한 데이터 처리
pause() & resume() 비동기 흐름 제어, 순서 보장 코드 복잡성 증가 정교한 흐름 제어 필요 시
pipeline() 에러 핸들링, 비동기 흐름 최적화 Node.js 10+ 필요 대규모 스트림 작업

📊 💡 최종 추천

  • 빠르게 끝내야 한다면 → destroy()
  • 비동기 흐름을 제어하고 싶다면 → pause() & resume()
  • 에러 핸들링과 확장성을 고려한다면 → pipeline()

💬 마무리

Node.js의 스트림은 효율적인 데이터 처리를 가능하게 하지만, 비동기 흐름 관리에 유의해야 합니다. 특히 대용량 CSV 처리에서는 pause()와 destroy()를 적절히 사용하여 메모리 사용을 줄이고, 필요 시 pipeline()으로 에러 핸들링까지 고려하세요. 🚀

 

#NodeJS #JavaScript #CSVParsing #StreamAPI #AsyncProgramming #DataProcessing #BackendDevelopment #NodeJSTips #FullStackDevelopment #WebDevelopment #Asynchronous #CodingTips #DeveloperLife #CodeOptimization #ErrorHandling #NodeJSTutorial #BackendEngineer #ProgrammingTips #SoftwareEngineering #TechBlog

💡 활용 팁:

  • 기술 태그 (#NodeJS, #JavaScript, #StreamAPI) → 개발자 타겟팅
  • 문제 해결 태그 (#ErrorHandling, #CodeOptimization) → 검색 최적화
  • 커뮤니티 태그 (#DeveloperLife, #TechBlog) → 더 넓은 도달범위 확보
728x90
반응형

+ Recent posts