반응형

require를 쓸까, import를 쓸까?
Node.js는 CommonJS(CJS)ECMAScript Modules(ESM) 두 방식을 모두 지원합니다. 이 글은 두 방식의 차이, 선택 기준, 상호 운용(interop), 마이그레이션 팁을 실무 중심으로 정리했습니다.


1) 빠른 요약: 언제 CJS, 언제 ESM?

  • CJS(CommonJS) — require, module.exports
    • 장점: 레거시/도구 호환 최고, 시작이 빠름
    • 언제: 기존 라이브러리/도구(CJS 중심)와의 호환이 최우선일 때
  • ESM(ECMAScript Modules) — import, export
    • 장점: 브라우저·번들러·TypeScript 생태계와 일관, 정적 분석/트리 셰이킹, Top-Level await
    • 언제: 신규 프로젝트 기본값으로 추천

실무 제안: 새 프로젝트는 ESM 통일 → 기존 CJS 패키지는 동적 import() 또는 createRequire로 연결.


2) 문법 & 실행 규칙 한눈에

A. CommonJS(CJS)

  • 파일 조건
    • 확장자 .cjs, 또는
    • package.json에 "type": "commonjs"(혹은 type 미지정)인 .js
  • 문법 예시
// module.cjs (또는 type: commonjs 환경의 module.js)
exports.abs = (n) => (n < 0 ? -n : n);
module.exports.util = () => 'ok';

// main.cjs
const mod = require('./module');    // .js 확장자 생략 가능
console.log(mod.abs(-3));
  • require 해석 규칙(요약)
    • require('./x') → ./x → ./x.js → ./x.json → ./x.node
    • 대상이 디렉터리면 package.json의 main/exports 우선 → 없으면 index.js(→.json→.node)
    • 확장자/디렉터리 자동 보정 O

B. ES Modules(ESM)

  • 파일 조건
    • 확장자 .mjs, 또는
    • package.json에 "type": "module"인 .js
  • 문법 예시
// module.mjs (또는 type: module 환경의 module.js)
export const abs = (n) => (n < 0 ? -n : n);

// main.mjs
import { abs } from './module.js';  // ESM은 확장자 명시가 원칙
console.log(abs(-3));
  • import 해석 규칙(요약)
    • 확장자/디렉터리 자동 보정 X → 경로에 확장자 필수
    • 패키지 import 시 package.json의 exports(서브패스 포함) 규칙을 엄격히 따름

3) package.json으로 프로젝트 기준 정하기

ESM 프로젝트(권장)

{
  "type": "module"
}

CJS 프로젝트(기본값과 동일)

{
  "type": "commonjs"
}

혼용 전략

  • 한 저장소에서 CJS와 ESM을 섞는 경우:
    • CJS 파일은 .cjs, ESM 파일은 .mjs 로 확장자 분리
    • 패키지 배포 시 exports에 조건부 내보내기(CJS/ESM 동시 제공) 구성

4) 상호 운용(Interop) — CJS ↔ ESM 함께 쓰기

CJS → ESM 불러오기 (동적 import)

// main.cjs
(async () => {
  const { abs } = await import('./module.mjs');
  console.log(abs(-3));
})();

 

ESM → CJS 불러오기 (default 형태로 옴)

// main.mjs
import legacy from './module.cjs';
const { abs } = legacy;

 

ESM에서 require 사용(특수 케이스)

// main.mjs
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const cjs = require('./legacy.cjs');
 

5) 자주 겪는 함정과 해결

exports vs module.exports (CJS)

  • exports.foo = ... (같은 객체를 가리킴)
  • exports = fn  (참조 교체 → 내보내기 끊김) → 객체 전체 교체는 module.exports = ...로

변수 이름 충돌

  • CJS의 내장 변수 module을 가리는 코드 X
// 나쁨
const module = require('./module');
// 권장
const myModule = require('./module');

경로/파일 상수 차이

  • CJS: __filename, __dirname 바로 사용 가능
  • ESM: import.meta.url 기반으로 계산
 
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname  = dirname(__filename);

 

 

확장자/디렉터리 보정

  • CJS(require): 자동 보정 O
  • ESM(import): 확장자 명시 필수, 디렉터리 import 금지

Top-Level await

  • ESM만 지원
// ESM에서만 가능
const data = await fetch(url).then(r => r.json());

 

6) 선택 가이드 & 마이그레이션 체크리스트

신규 프로젝트

  • ESM 통일("type": "module")
  • 브라우저/번들러/TS와 규칙 일치, 정적 분석/트리 셰이킹, Top-Level await

기존 CJS 유지

  • 도구/레거시 호환 최우선
  • ESM 패키지 연동: 동적 import(), createRequire 브리지 고려

점진 전환 체크리스트

  • package.json에 "type": "module" 추가, CJS 파일은 .cjs로 분리
  • 확장자 명시(ESM 규칙) 및 디렉터리 import 제거
  • __dirname/__filename → import.meta.url 기반 대체
  • 외부 패키지의 exports/서브패스 호환 확인
  • 빌드/테스트/번들러 설정(ESM 대응) 점검

7) 유틸 스니펫

CJS: 실제 로드 경로 확인

console.log(require.resolve('./module'));

ESM: JSON 가져오기(Node 20+)

import data from './data.json' with { type: 'json' };
 

ESM: CommonJS 패키지 가져오기 패턴

import cjsDefault from 'legacy-pkg';
import * as cjsAll from 'legacy-pkg';

8) 결론

  • Node.js는 CJS와 ESM을 모두 지원합니다.
  • 새 프로젝트는 ESM로 통일, 레거시/도구 호환이 중요하면 CJS 유지도 합리적입니다.
  • 혼용 시 .cjs/.mjs 분리, 동적 import(), **createRequire**로 안전하게 연결하세요.
 
728x90
반응형
반응형

JavaScript에서 fallback 패턴은 일반적으로 어떤 작업이 실패하거나 특정 기능이 지원되지 않을 경우, 다른 방법을 제공하여 프로그램이 정상적으로 동작하도록 만드는 방식을 의미합니다. 이를 통해 코드가 예외를 처리하거나 대체 경로로 진행될 수 있게 합니다.

fallback 패턴은 다음과 같은 상황에서 유용하게 사용됩니다:

  1. 브라우저 호환성: 최신 브라우저에서만 지원하는 기능이 있을 때, 오래된 브라우저에서는 이를 대체할 수 있는 방식으로 코드를 처리하는 경우.
  2. API 호출 실패: 외부 API를 호출했을 때 실패할 경우, 로컬 데이터를 사용하거나 기본값을 제공하는 방식.
  3. 옵션이 없는 경우 기본값 제공: 사용자가 특정 값을 제공하지 않았을 때 기본값을 제공하는 경우.

1. 기본값 제공 (Fallback to Default Value)

기본값을 제공하는 패턴은 자주 사용됩니다. 예를 들어, 함수에 인자가 전달되지 않았을 때 기본값을 설정하는 방식입니다.

예시

function greet(name = "Guest") {
  console.log(`Hello, ${name}!`);
}

greet(); // Hello, Guest!
greet("Alice"); // Hello, Alice!
  • 위 코드에서 name 파라미터가 전달되지 않으면, "Guest"라는 기본값이 사용됩니다.

2. 조건문을 통한 Fallback 처리

어떤 작업이 실패할 때 대체 작업을 수행하는 패턴입니다. 예를 들어, 특정 기능이 브라우저에서 지원되지 않을 경우, 대체할 기능을 제공하는 방식입니다.

예시

if ('localStorage' in window) {
  // localStorage를 지원하는 경우
  localStorage.setItem('user', 'John Doe');
} else {
  // localStorage를 지원하지 않는 경우
  console.log("localStorage is not supported, using cookies as fallback.");
  document.cookie = "user=John Doe; path=/";
}
  • 이 예시에서는 localStorage가 브라우저에서 지원되지 않으면, 쿠키를 사용하여 데이터를 저장합니다.

3. try-catch 구문을 통한 Fallback 처리

API 호출이나 외부 리소스 접근 시 실패할 수 있는 부분에서 try-catch 구문을 사용하여 예외를 처리하고, 실패 시 대체 방법을 제공하는 패턴입니다.

예시

try {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error("API call failed. Using local data instead.");
  const localData = { name: "Fallback Name", age: 30 };
  console.log(localData);
}
  • API 호출이 실패할 경우, catch 블록에서 로컬 데이터를 사용하여 대체합니다.

4. Optional Chaining과 Nullish Coalescing을 통한 Fallback

JavaScript의 최신 문법인 optional chaining (?.)과 nullish coalescing (??) 연산자를 사용하여, 객체나 배열의 값이 null 또는 undefined일 때 fallback 값을 제공할 수 있습니다.

예시

const user = {
  name: "Alice",
  address: {
    city: "Wonderland",
  },
};

// Optional chaining과 nullish coalescing을 사용하여 fallback 처리
const city = user.address?.city ?? "Unknown City";
console.log(city); // Wonderland

const country = user.address?.country ?? "Unknown Country";
console.log(country); // Unknown Country
  • user.address?.city는 address가 null이나 undefined일 경우, undefined를 반환하고, 그 후 ?? 연산자를 통해 fallback 값 "Unknown City"가 사용됩니다.

5. 대체 함수 사용 (Fallback to Alternative Function)

어떤 함수나 메서드가 실패했을 경우, 다른 대체 함수를 호출하여 처리하는 방법입니다. 예를 들어, 데이터베이스 접근 실패 시 로컬 캐시에서 데이터를 가져오는 방식입니다.

예시

function fetchDataFromAPI() {
  return fetch('https://api.example.com/data').then(response => response.json());
}

function fetchDataFromCache() {
  return { id: 1, name: 'Fallback Data' };
}

function getData() {
  return fetchDataFromAPI()
    .catch((error) => {
      console.error('API call failed, falling back to cache.');
      return fetchDataFromCache();
    });
}

getData().then(data => console.log(data));
  • fetchDataFromAPI() 호출이 실패하면, fetchDataFromCache()가 호출되어 대체 데이터를 제공합니다.

결론

JavaScript에서 fallback 패턴은 여러 상황에서 실패를 처리하고 대체 경로를 제공하는 중요한 기법입니다. 이는 코드의 안정성을 높이고, 다양한 환경에서도 코드가 예기치 않게 동작하지 않도록 보장해줍니다. 올바르게 사용하면 브라우저 호환성 문제나 외부 API 호출 실패 등 다양한 예외 상황을 처리할 수 있습니다.

 
 
728x90
반응형
반응형

JavaScript에서 숫자 계산을 할 때, 소수점을 처리하는 다양한 수학 함수들이 있습니다. 그 중 Math.ceil() 함수는 주어진 숫자보다 크거나 같은 가장 작은 정수를 반환하는 함수로, 주로 올림 처리에 사용됩니다. 이번 포스트에서는 Math.ceil() 함수의 사용법, 동작 원리, 그리고 예시를 함께 살펴보겠습니다.

1. Math.ceil() 함수란?

Math.ceil() 함수는 주어진 숫자보다 크거나 같은 가장 작은 정수를 반환합니다. 이는 소수점을 처리할 때, 항상 올림 처리를 하여 결과를 정수로 변환하는 함수입니다.

구문:

Math.ceil(x)
  • x: 올림할 숫자입니다. 이 숫자는 정수든 실수든 상관없습니다.

2. Math.ceil()의 동작 원리

Math.ceil() 함수는 다음과 같은 방식으로 동작합니다:

  • 양수일 때: 숫자가 소수점을 포함하고 있으면, 해당 숫자보다 크거나 같은 가장 작은 정수로 변환합니다.
  • 음수일 때: 음수일 경우에도 올림 처리가 적용되며, 그 절댓값이 더 큰 방향으로 올림됩니다.

3. 예시

Math.ceil() 함수의 동작을 예시를 통해 더 자세히 살펴보겠습니다.

console.log(Math.ceil(4.1));   // 5
console.log(Math.ceil(4.9));   // 5
console.log(Math.ceil(-4.1));  // -4
console.log(Math.ceil(-4.9));  // -4
console.log(Math.ceil(0.0001)); // 1

결과 설명:

  • Math.ceil(4.1)은 4.1보다 크거나 같은 가장 작은 정수인 5를 반환합니다.
  • Math.ceil(4.9)는 4.9보다 크거나 같은 가장 작은 정수인 5를 반환합니다.
  • Math.ceil(-4.1)은 -4.1보다 크거나 같은 가장 작은 정수인 -4를 반환합니다. (음수도 올림 처리)
  • Math.ceil(-4.9)는 -4.9보다 크거나 같은 가장 작은 정수인 -4를 반환합니다.
  • Math.ceil(0.0001)은 0.0001보다 크거나 같은 가장 작은 정수인 1을 반환합니다. (소수점이 매우 작은 값일지라도 올림 처리)

4. Math.ceil()과 Math.floor() 비교

Math.ceil()과 Math.floor()는 비슷한 역할을 하지만, 차이점이 있습니다. Math.ceil()은 올림 처리를 하여 숫자를 "위로" 변환하고, Math.floor()는 내림 처리를 하여 숫자를 "아래로" 변환합니다.

console.log(Math.ceil(4.1));   // 5
console.log(Math.floor(4.1));  // 4

5. Math.ceil() 사용 용도

Math.ceil() 함수는 다양한 상황에서 유용하게 사용될 수 있습니다. 예를 들어:

  • 페이지네이션: 데이터가 여러 페이지에 걸쳐 표시될 때, 데이터의 개수를 페이지 수로 나눈 후 올림 처리를 통해 마지막 페이지를 계산할 때 사용합니다.
  • 금액 계산: 사용자가 결제할 금액을 올림 처리하여 결제 금액이 항상 일정하게 유지될 수 있도록 할 때 유용합니다.
  • 시간 계산: 특정 시간 단위로 올림 처리하여 최소 시간을 계산할 때 사용됩니다.

결론

Math.ceil() 함수는 JavaScript에서 숫자를 올림 처리하여 가장 작은 정수로 변환할 수 있는 유용한 함수입니다. 양수와 음수 모두에서 올림 처리가 적용되며, 소수점 값이 매우 작은 경우에도 유용하게 사용할 수 있습니다. 다양한 계산에서 활용할 수 있기 때문에, 숫자 처리에 있어 중요한 역할을 합니다.

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
반응형
반응형

기본적으로 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