본문 바로가기

BackEnd/Node

[노드교과서] 섹션 2. 노드 기본 기능 익히기

REPL

 ⇒ 자바스크립트는 스크립트 언어라 즉석에서 코드 실행이 가능하다.

  - REPL이란 입력된 명령어를 읽어(Read) 평가하고(Eval) 평가한 결과를 출력(Print) 후 다시 명령어 입력을

    과정을 반복(Loop)하는 과정을 의미한다.

  - 윈도우의 명령 프롬프트 / 맥 or 리눅스의 터미널에 node 입력 등

 

ex) node console에서 REPL의 과정의 예

> const str = 'Hello node';
undefined
> console.log(str);
'Hello node'
undefined

 

ex2) 윈도우 명령 프롬프트에서 js파일을 실행시키는 예시

Microsoft Windows [Version 10.0.19045.3693]
(c) Microsoft Corporation. All rights reserved.
C:\nodejs>node helloWorld   
Hello world
hello node
C:\nodejs>

 

모듈

 ⇒ 노드는 자바스크립트 코드를 모듈로 만들 수 있다.

  - 모듈은 특정한 기능을 하는 함수나 변수들의 집합이며 모듈로 구현 시 여러 프로그램에서 재 사용이 가능하다.

  - 모듈은 파일 끝에 module.exports로 모듈로 만들 값을 지정한다.

  - 지정한 모듈의 값을 다른 파일에서 require(파일경로)로 그 모듈의 내용을 가져와 사용할 수 있다.

 

  ex)모듈의 파일 구성 예시

var.js
const odd = '홀수';
const even = '짝수';

// 객체를 module.exports에 대입
module.exports = { odd, even };   

// module.exports와 exports는 같다.(빈객체)
// 한가지만 exports하고 싶을때는 module.exports를 사용한다.
// 두가지 이상을 exports하고 싶을 때는 
// exports.add = add; exports.even = even;
// module.exports = { odd, even };  
// 두 방식중 하나를 사용한다.
// module.exports === exports === {}
func.js
// require를 이용해 모듈을 참조한다.
const value = require('./var.js');
value.even;
value.odd;

// 구조분해 할당을 통해 모듈로 얻어온 객체의 값을 선언한다.
const {odd, even} = require('./var.js');
console.log(value, odd, even);

function checkOddOrEven(number) {
    if(number % 2) {
        return odd;
    } else {
        return even;
    }
}

// module.export는 하나의 파일에서 한번만 써울 수 있다.
module.exports = checkOddOrEven;
index.js
const {odd, even} = require('./var');
const checkNumber = require('./func');

function checkStringOddOrEven(str) {
    if(str.length % 2) {
        return odd;
    } else {
        return even;
    }
}

console.log(checkNumber(10));
console.log(checkStringOddOrEven('hello'));
cmd
C:\nodejs>node func 
{ odd: '홀수', even: '짝수' } 홀수 짝수

C:\nodejs>node index
{ odd: '홀수', even: '짝수' } 홀수 짝수
짝수      
홀수      
C:\nodejs>

 

this

 ⇒ 노드에서 this 사용시 정말 주의해야 할 점이 존재한다.

  - 최상위 스코프의 this는 module.exports를 가르킨다.

  - 그 외에는 브라우저의 자바스크립트와 동일하다.

  - 함수 선언문 내부의 this는 global(전역) 객체를 가르킨다.

console.log(this === global);          // false
console.log(this === mobule.exports);  // true
function a() {
	console.log(this === global);      // true
}
a();

 

require의 특성

 ⇒ 몇 가지 숙지해둬야 할 속성들이 존재한다.

  - require가 소스 최상단에 올 필요는 없다.

  - require.main과 module은 동일하다.

  - require.cache에는 한번 require한 모듈에 대한 캐싱 정보가 들어있다.

   그러므로  다음번 require 선언 시 다시 파일을 불러오는 것이 아니라 캐시(메모리)에서 불러온다.

  - require.main은 노드 실행 시 첫 모듈을 가르킨다.

module.exports = '저를 찾아보세요';

// 선언을 안하면 다른 파일을 실행만 하고 변수에 담지 않는다.
// require는 소스 가장 위에 있지 않아도 된다.
require('./var'); 

console.log('require.cache입니다.');
console.log(require.cache);
console.log('require.main입니다.');
console.log(require.main === module);  // true
console.log(require.main.filename)     // c:\nodejs\require.js

 

순환참조

 ⇒ 두 개의 모듈이 서로를 require하는 상황을 조심해야 하며 위 상황을 순환참조라 부른다.

  - dep1이 dep2를 require하고, dep2가 dep1을 requrie함

  - dep1 module.exports가 지정한 값이 아닌 빈 객체로 dep2에 참조가 된다.(무한반복을 막기 위한 의도)

  - 순환참조라는 상황은 무한루프를 야기하기 때문에 해당 상황이 존재하지 않도록 소스 구성을 하자.

dep1.js
require('./dep2');

module.exports = {
    hello : 'zerocho'
}

dep2.js
// 순환참조를 막아주기 위해 빈객체를 반환한다.
// exrpots한 값을 얻어올 수 없다.
require('./dep1');  // {}

 

ECMAScript

 ⇒ ECMAScript 모듈(ES모듈)은 공식적인 자바스크립트 모듈 형식이다.

  - 노드에서는 현재까진 CommonJS모듈을 많이 쓰지만, ES모듈이 정해지면서 점차 ES모듈 사용 비중이 높아지고 있다.

  - 특히 브라우저에서도 ES모듈을 사용 가능하기에 브라우저와 노드 모두 같은 형식을 띌 수 있는 장점이 있다.

  - commonJs에서는 requrie, module, exports는 객체이기 때문에 참조를 끊거나 객체를 엎어씌울수 있지만 

   ES모듈의 export, default는 예약어이기 때문에 사용자가 직접 제어할 수 없다.

// var.mjs
export const odd = 'mjs 홀수입니다.';  // commonJs = export.odd = 'mjs 홀수입니다.';
export const even = 'mjs 짝수입니다.';

// func.mjs

import { odd, even } from './var.mjs';  // commonJs = const {odd, even} require('./var.cjs');

function checkOddOrEven(num) {
    if(num %2) {
        return odd;
    } else {
        return even;
    }
}

export default checkOddOrEven;  // commonJs = module.exrpots = checkOddOrEven;

// index.mjs
import { odd, even } from './var.mjs';
import checkNumber from './func.mjs';

function checkStringOddOrEven(str) {
    if(str.length %2) {
        return odd;
    } else {
        return even;
    }
}

console.log(checkNumber(10));               // mjs 짝수입니다.
console.log(checkStringOddOrEven('hello')); // mjs 홀수입니다.

 

다이나믹 임포트

  ⇒ commonJs에서는 require를 어디에서든 선언해 사용할 수 있다.

const a = false;
if (a) {
    require('./func');
}

console.log('성공');

  ⇒ 하지만 ES 모듈에서는 첫번째 라인이 아닌 다른곳에서 import를 그냥 사용시에는 오류가 발생한다.

     해당 오류를 해결하기 위해 import()함수를 호출해 어디서든 해당 소스를 실행할 수 있다.

const a = true;
if (a) {
    // import './func.mjs';  // SyntaxError: Unexpected string
    // import()함수는 Promise이다.
    // 그렇기 때문에 await 넣어야한다.
    // top level await가 있다.
    const m1 = await import('./func.mjs');  // export의 default가 commonJS의 mobule.exports와는 다르다.
                                            // requrie mobule.exports = [Function: checkOddOrEven]
                                            // import = { default: [Function: checkOddOrEven] } 
    console.log(m1);
    const m2 = await import('./var.mjs');
    console.log(m2);
}
console.log('성공');

 

__filename, __dirname

⇒ commonJS 기준 노드 소스에 해당 키워드를 넣고 실행  시 현재 파일명/파일경로를 얻어올 수 있다.

  하지만, ES모듈에서는 해당 키워드를 사용할 수 없다.

 

3.4 노드 내장 객체 알아보기

 1. global

   ⇒ 노드의 전역 객체이다.

    - 브라우저의 window와 같은 역할을 하며 모든 파일에서 접근이 가능하다.

    - javascript의 window처럼 생략도 가능하다(console, require도 global 속성)

    - global 속성에 값을 대입 시 다른 파일에서도 사용이 가능하다.  하지만, 해당 속성을 어디서 부여하는지

     알 방도가 없어 관리가 어렵기에 선호하지 않는다.

// globalA.js 파일
module.exports = () => globalThis.message;
// globalB.js 파일
const A = require('./globalA');
globalThis.message = '안녕하세요';
console.log(A());

 2. console 객체

  - console.time, console.timeEnd(퍼포먼스 체크)

   : 소스 위치의 time지점부터 timeEnd지점까지의 시간이 얼마나 걸리는지 체크할 수 있다.

  - console.log(로그), console.error(에러로깅), console.dir(객체로깅(console.dir({hello:'hello'});))

  - console.trace(호출스택 로깅)

   : 자바 스크립트의 호출 스택 로깅을 확인할 수 있다.

const string = 'abc';
const number = 1;
const boolean = true;
const obj = {
    outSide : {
        inSide: {
            key : 'value',
        },
    },
};
console.time('전체 시간');
console.log('평범한 로그 출력');
console.log(string, number, boolean);
console.error('에러 메시지는 console.error 사용');
console.table([{ name : '제로', birth : 1994}, {name : '재익', birth: 1986}]);
console.dir(obj, {colors : false, depth : 2});
console.dir(obj, {colors : true, depth : 1});
console.time('시간 측정');
for(let i = 0; i < 100000; i++) {}
console.timeEnd('시간 측정');
function b() {
    console.trace('에러 위치 추적');
}
function a() {
    b();
}
a();
console.timeEnd('전체 시간');

 

 3. 타이머 메서드

  ⇒ 타이머 set 메서드에 clear 메서드가 매칭되며 set 메서드의 리턴 값(아이디)으로 clear 메서드에 넣어 취소할 수 있다.

   - setTimeout(콜백함수, 밀리초) : 주어진 밀리초(1000분의 1초) 이후에 콜백함수를 실행한다.

   - setIntarval(콜백함수, 밀리초) : 주어진 밀리초보다 콜백함수를 실행한다.

   - setImmediate(콜백함수) : 콜백함수를 즉시 실행하며 즉시 실행 시 setTimeout(() => console.log('hello',0)과 동일하다.

    : 굳이 타이머 메서드를 사용하지 않고 즉시 실행하는 것과 무엇이 다른지 의문점이 생길 수 있다.

    타이머 메서드는 비동기 메서드이며 특정 함수들을 백 그라운드에 모아 한번에 실행이 필요한 경우가 있다.

    해당 경우에 타이머 필요 없을 시 setImmediate 함수를 사용하여 백 그라운드로 특정 함수들을 한번에 보내 처리한다.

const timeout = setTimeout(() => {
    console.log('1.5초후 실행');
},1500);

// 하단의 clearInterval로 인해 2번만 수행된다.
const interval = setInterval(()=> {
    console.log('1초마다 실행');
},1000); 

// 하단의 clearTimeout으로 인해 수행되지 않는다.
const timeout2 = setTimeout(() => {
    console.log('setTimeclear로 제거예정');
}, 3000);

setTimeout(() => {
    clearTimeout(timeout2);
    clearInterval(interval);
}, 2500);

// setImmediate는 수행되지 않는다.
// Promise인 타이머함수의 실행 순서는 다음과 같다.
// 호출스택 -> 백그라운드 -> 태스트큐 -> 호출스택순이기 때문이다.
const immediate = setImmediate(() => {
    console.log('즉시 실행');
});


const immediate2 = setImmediate(() => {
    console.log('즉시 실행');
});
clearImmediate(immediate2);
// 즉시 실행
// 1초마다 실행
// 1.5초후 실행
// 1초마다 실행

 

 12. process

  ⇒ 현재 실행중인 노드 프로세스에 대한 정보를 담고 있다.

   - 컴퓨터마다 출력 값이 다를수도 있다.  왜냐하면 프로세스가 실제 실행되는 환경을 보여주기 때문.

C:\nodejs>node
Welcome to Node.js v21.4.0.
Type ".help" for more information.
> process.version      // 설치된 노드의 버전
'v21.4.0'
> process.arch         // 프로세서 아키텍처 정보. arm, ia32 등의 값일수도 있다.
'x64'
> process.platform     // 운영체제 플랫폼 정보. linux나 darwin, freebsd 등의 값일수도 있다.
'win32'
> process.pid          // 현재 프로세스의 아이디이다.  프로세스를 여러 개 가질 때 구분할 수 있다.
19756
> process.uptime()     // 프로세스가 시작된 후 흐른 시간이다.
74.3572628
> process.execPath     // 노드의 설치되어 있는 경로이다.
'C:\\Program Files\\nodejs\\node.exe'
> process.cwd()        // 현재 프로세스가 실행되는 위치이다.
'C:\\nodejs'
> process.cpuUsage()   // 현재 CPU 사용량
{ user: 765000, system: 625000 }

 

 13. process.env

  ⇒ 시스템 환경 변수들이 들고있는 객체

   - 비밀키(데이터베이스 비밀번호, 서드파트 앱 키 등)를 보관하는 용도로 쓰임

   - 환경변수는 process.env로 접근이 가능하다.

   const secretId = process.env.SECRET_ID;
   const secretCode = process.env.SECRET_CODE;

 

  - 일부 환경변수는 노드 실행 시 노드 환경에 영향을 줄 수 있다.

    ex) NODE_OPTIONS(노드실행옵션), UV_THREADPOOL_SIZE(스레드풀 개수)
	
	// --max-old-space-size는 노드가 사용할 수 있는 메모리를 지정한다.
	NODE_OPTIONS=--max-old-space-size=8192
	UV_THREADPOOL_SIZE=8

 14. process.nextTick(콜백)

  ⇒ 이벤트 루프의 다른 콜백 함수들보다 nextTick의 콜백함수를 우선적으로 처리한다.

   - promise와 nextTick은 마이크로 태스크에서 수행되므로 실행순서가 순차적으로 보장된다.

   - 마이크로 태스크는 일반 태스크보다 우선순위가 높아 setTimeout, setImmediate가 후순위다.

setImmediate(() => {
    console.log('immediate');
});
process.nextTick(() => {
    console.log('nextTick');
});
setTimeout(() => {
    console.log('timeout');
},0);
Promise.resolve().then(() => console.log('promise'));
// nextTick -> promise -> timeout -> immediate
// nextTick과 promise는 마이크로 태스크이기 때문에 순서가 정해져 있다.
// 하지만, timeout과 immediate는 순서가 환경에 따라 달라질 수 있다.
// 그러므로 setImmediate만 사용하는 편이 좋다.


 15. process.exit(코드)

  ⇒ 현재의 프로세스를 멈추며 코드가 없거나 0이면 정상종료이며 그 외의 코드는 비정상종료를 의미한다.

 

3.5 노드 내장 모듈 알아보기

 1. os

  ⇒ 운영체제의 정보를 담고 있는 내장 모듈이다.

   - 모듈은 require로 가져올 수도 있다.(내장모듈이라 경로 대신 이름만 적어줘도 된다.)

 

 2. os모듈의 메서드

const os = require('os');
console.log('운영체제 정보-----------------------------');
console.log('os.arch(): ', os.arch());          // x64(process.arch와 동일)
console.log('os.platform(): ', os.platform());  // win32(process.platform과 동일)
console.log('os.type(): ',os.type());           // Window_NT(운영체제의 종류)
console.log('os.uptime(): ',os.uptime());       // 38727.328(운영체제 부팅 이후 흐른 시간(초))
console.log('os.hostname(): ',os.hostname());   // DESKTOP-EOFDG1G(컴퓨터의이름)
console.log('os.release(): ',os.release());     // 10.0.19045(운영체제의 버전)
console.log('경로-------------------------------------');
console.log('os.homedir(): ',os.homedir());     // C:\Users\jikim(홈 디렉토리 경로)
console.log('os.tmpdir(): ',os.tmpdir());       // C:\Users\jikim\AppData\Local\Temp(임시파일저장경로)

console.log('cpu정보----------------------------------');
console.log('os.cpus(): ',os.cpus());               // cpu의 코어정보
console.log('os.cpus().length: ',os.cpus().length); // cpu 코어개수
console.log('메모리정보--------------------------------');
console.log('os.freemem(): ',os.freemem());         // 사용 가능한 메모리(RAM)을 보여준다.
console.log('os.totalmem(): ',os.totalmem());       // 전체 메모리 용량을 보여준다.

 3. path

  ⇒ 폴더와 파일의 경로를 쉽게 조작하도록 도와주는 모듈이다.

   - 운영체제별로 경로 구분자가 다르다.(Window : ' \ ' , POSIX : ' / ') 

 4. path 모듈 메서드

const path = require('path');
const fileName = __filename;
// 윈도우는 C:\users\zerocho 
// 맥은 C:/user/zerocho

// window나 POSIX(max,linux)의 /,\를 os따라 맞는걸로 처리해준다.
// console.log(__dirname);
// console.log(path.join(__dirname, '..', '/var.js'));    // ..입력 시 상위경로
// console.log(path.resolve(__dirname, '..', '/var.js')); // ..입력 시 상위경로
console.log(path.join(__dirname, '/var.js'));    // join은 절대경로를 무시한다.
console.log(path.resolve(__dirname, '/var.js')); // resolve는 절대경로가 들어있으면 앞의경로를 무시한다.

console.log('path.sep: ', path.sep);                      // \(경로의 구분자이다.)
console.log('path.delimiter: ', path.delimiter);          // ;(환경 변수의 구분자이다.)
console.log('path.dirname(): ',path.dirname(fileName));   // C:\nodejs(파일이 위치한 경로)
console.log('path.extname(): ',path.extname(fileName));   // .js(파일의 확장자)
console.log('path.basename(): ',path.basename(fileName)); // path.js(파일의이름(확장자포함))
console.log('path.basename - extname: ',path.basename(fileName, path.extname(fileName)));  // path
// {root: 'C:\\',dir: 'C:\\nodejs',base: 'path.js',ext: '.js',name: 'path'}
console.log('path.parse()', path.parse(fileName));   // 파일경로를 root, dir, base, ext, name으로 분리
console.log('path.normalize()', path.normalize('c://nodejs\\\path.js'));  // /나 \를 정상적인 경로로 변경처리
// isAbsolute는 현재 경로가 절대경로일 경우 true를 반환한다.
console.log('path.isAbsolute("c:\\")', path.isAbsolute('c:\\'));        // true
console.log('path.isAbsolute("./nodejs)', path.isAbsolute('./nodejs')); // false

 5. 알아둬야할 path 관련 정보

  ⇒ join과 resolve의 차이 : resolve는 ' / ' 를 절대경로로 처리한다.

                                           join은 상대경로로 처리한다.

   - 상대 경로 : 현재 파일 기준.  같은 경로면 점 하나( . ), 한 단계 상위 경로면 점 두개( .. )

   - 절대 경로는 루트 폴더나 프로세스가 실행되는 위치가 기준이다.

   path.join('/a','/b','c');   // /a/b/c
   path.resolve('/a,'/b','c'); // b/c   // a는 resolve에서는 절대경로이다.(/a)

  ⇒ \\ 와 \ 의 차이 : \ 는 윈도우의 경로 구분자, \\ 는 자바스크립트 문자열 안에서 사용된다.

  ⇒ 윈도우에서 POSIX path를 사용 : path.posix 객체를 사용하면 된다.

  ⇒ POSIX에서 윈도우 path를 사용 : path.win32 객체를 사용하면 된다.

 

 6. url

  ⇒ 인터넷 주소를 쉽게 조작하도록 도와주는 모듈이다.

   - url 처리방식은 노드 버전 7에서 추가된 WHATWG(웹 표준을 정하는 단체명) url과 과거 노드 사용 url 방식이 있다.

    현재는 WHATWG url 방식만 사용한다.

   - WHATWG방식은 브라우저에서도 동일하게 사용되므로 호환성이 좋다.

 

[노드교과서 참고]

   - url 모듈 안에 URL 생성자가 있어 이 생성자에 주소를 넣어 객체로 만들면 부분별로 정리가 된다.

   - 해당 방식이 WHATWG의 url이며 username, password, origin, searchParams 속성이 존재한다.

// 내장객체이므로 require를 사용할 필요가 없다.
const url = require('url');
const {URL} = url;

const myURL = new URL('https://class.tsherpa.co.kr/board/add.html?category_id=613245&content_id=1931654&classNo=7174&fromBoardAll=Y')
console.log('new URL(): ', myURL);
// new URL():  URL {
//     href: 'https://class.tsherpa.co.kr/board/add.html?category_id=613245&content_id=1931654&classNo=7174&fromBoardAll=Y',
//     origin: 'https://class.tsherpa.co.kr',
//     protocol: 'https:',
//     username: '',
//     password: '',
//     host: 'class.tsherpa.co.kr',
//     hostname: 'class.tsherpa.co.kr',
//     port: '',
//     pathname: '/board/add.html',
//     search: '?category_id=613245&content_id=1931654&classNo=7174&fromBoardAll=Y',
//     searchParams: URLSearchParams {
//       'category_id' => '613245',
//       'content_id' => '1931654',
//       'classNo' => '7174',
//       'fromBoardAll' => 'Y' },
//     hash: ''
//   }
console.log('url.format(): ', url.format(myURL));
// https://class.tsherpa.co.kr/board/all.html?classNo=7174

 

7. searchParams
  >> queryString을 searchParams로 전환하는게 좋다.

const myURL = new URL('https://class.tsherpa.co.kr/board/add.html?category_id=613245&content_id=1931654&classNo=7174&fromBoardAll=Y&category_id=2029029');
console.log('searchParams:', myURL.searchParams);                              // {}

console.log('searchParams.get()',myURL.searchParams.get('category_id'));       // '613245' key에 대한 첫번째 값만 얻어온다.
console.log('searchParams.getAll()',myURL.searchParams.getAll('content_id'));  // ['193165']
console.log('searchParams.getAll()',myURL.searchParams.getAll('category_id')); // ['613245','2029029']
console.log('searchParams.has()',myURL.searchParams.has('fromBoardAll'));      // true(속성존재여부)
console.log('searchParams.has()',myURL.searchParams.has('notAttribute'));      // false


// keys or values 키 혹은 값에 대해서만 반복기(iterator(ES2015문법)) 객체로 가져온다.
// URLSearchParams Iterator {'category_id','content_id','classNo','fromBoardAll','category_id'}
console.log('searchParams.values()',myURL.searchParams.keys('category_id'));
// URLSearchParams Iterator { '613245', '1931654', '7174', 'Y', '2029029' }
console.log('searchParams.values()',myURL.searchParams.values('category_id'));

// append는 속성을 연속적으로 추가할 수 있다.
myURL.searchParams.append('filter', 'es3');
myURL.searchParams.append('filter','es5');
console.log(myURL.searchParams.getAll('filter'))  // ['es3', 'es5']

// set은 key가 동일할 경우 기존 값은 초기화되고 새로운 값이 들어간다.
myURL.searchParams.set('filter','es6');
console.log(myURL.searchParams.getAll('filter')); // ['es6']

// delete는 해당 key를 삭제한다.
myURL.searchParams.delete('filter');
console.log(myURL.searchParams.getAll('filter')); // []

// category_id=613245&content_id=1931654&classNo=7174&fromBoardAll=Y&category_id=2029029
console.log('searchParams.toString() ',myURL.searchParams.toString());
myURL.search = myURL.searchParams.toString();



 8. dns
  >> DNS를 다룰 때 사용하는 모듈이다.  주로 도메인을 통해 IP, 기타 DNS 정보를 얻고자 할때 사용한다.

import dns from 'dns/promises';
const ip = await dns.lookup('class.tsherpa.co.kr');
console.log('IP',ip); // ip주소를 얻어온다
const a = await dns.resolve('class.tsherpa.co.kr', 'A');
console.log('A',a);   // ipv4 주소
const mx = await dns.resolve('class.tsherpa.co.kr','MX');
console.log('MX',mx); // 메일서버
const cname = await dns.resolve('www.class.tsherpa.co.kr','CNAME');
console.log('CNAME',cname); // 별칭, (주로 www.가 붙는 주소는 별칭인 경우가 많음)
const nay = await dns.resolve('class.tsherpa.co.kr','ANY');
console.log('ANY',nay);    // 그외의 정보들


 
 13. 단방향 암호화(crypto)
  >> 암호화는 가능하지만 복호화는 불가능하다.
   - 암호화 : 평문을 암호로 만듦.
   - 복호화 : 암호를 평문으로 해독
   
  >> 단방향 암호화의 대표 주자는 해시 기법(ex)비밀번호)
   - 문자열을 고정된 길이의 다른 문자열로 바꾸는 방식
    ex) abcdefgh 문자열 -> qvew

   - 비밀번호 ------> 해쉬 함수 ------> 다이제스트
           <--x---         <------

  14. Hash 사용하기(sha512 추천)
   >> createHash(알고리즘): 사용할 해시 알고리즘을 넣는다.
    - md5, sha1, sha256, sha512등이 가능하지만, md5, sha1은 보안 취약점이 발견 되었다.
- 현재는 sha512정도로 충분하지만, 나중에 sha512마저도 취약해지면 더 강화된 알고리즘으로 바꿔야한다.
   >> update(문자열) : 변환할 문자열을 넣어준다.
   >> digest(인코딩) : 인코딩할 알고리즘을 넣어준다.
    - base64, hex, latin1이 주로 사용되는데, 그중 base64가 결과 문자열이 가장 짧아 애용된다.
 결과물로 변환된 문자열을 반환한다.

const crypto = require('crypto');

console.log('base64', crypto.createHash('sha512').update('비밀번호').digest('base64'));
console.log('base64', crypto.createHash('sha512').update('비밀번호').digest('hex'));
console.log('base64', crypto.createHash('sha512').update('다른 비밀번호').digest('base64'));
// base64 dvfV6nyLRRt3NxKSlTHOkkEGgqW2HRtfu19Ou/psUXvwlebbXCboxIPmDYOFRIpqav2eUTBFuHaZri5x+usy1g==
// base64 76f7d5ea7c8b451b773712929531ce92410682a5b61d1b5fbb5f4ebbfa6c517bf095e6db5c26e8c483e60d8385448a6a6afd9e513045b87699ae2e71faeb32d6
// base64 cx49cjC8ctKtMzwJGBY853itZeb6qxzXGvuUJkbWTGn5VXAFbAwXGEOxU2Qksoj+aM2GWPhc1O7mmkyohXMsQw==


 
  15. pbkdf2
   >> 컴퓨터의 발달로 기존 암호화 알고리즘이 위협받고 있다.
    - sha512가 취약해지면 sha3으로 넘어가야 한다.
    - 현재는 pbkdf2나, bcrypt, scrypt 알고리즘으로 비밀번호를 암호화한다.
    - Node는 pbkdf2와 scrypt를 지원한다.

const crypto = require('crypto');

crypto.randomBytes(64, (err, buf) => {
    // crypto.randomBytes로 64바이트 문자열 생성 -> salt 역할
    const salt = buf.toString('base64');
    // pbkdf2 인수로 순서대로 비밀번호, salt, 반복 횟수, 출력 바이트, 알고리즘 입력
    // 반복 횟수를 조정해 암호화하는데 1초 정도 걸리게 맞추는 것이 권장된다.	
    crypto.pbkdf2('비밀번호', salt, 100000, 64, 'sha512',(err,key) => {
        console.log('password : ', key.toString('base64'));
    });
});


   
  16. 양방향(대칭형) 암호화
   >> 키 관리를 할 경우 AWS Key Management Service이런것들도 사용한다.
   crypto.js를 통해 암호화처리를 하는게 유용하다.
   https://www.npmjs.com/package/crypto-js
   - 대칭형 암호화는 AES를 추천한다.
   - 비대칭 암호화는 RSA를 추천한다.
   
  17. util

const util = require('util');
const crypto = require('crypto');

// util.deprecate의 첫 번째 인자로 넣은 함수를 사용했을 때 경고 메시지를 출력한다.
// util.deprecate의 두 번째 인자에 경고메시지를 넣는다.
const dontUserMe = util.deprecate((x,y) => {
    console.log(x+y);
}, 'dontUserMe 함수는 deprecated되었으니 더 이상 사용하지 마세요!');
dontUserMe(1,2);



const randomBytesPromise = util.promisify(crypto.randomBytes);
randomBytesPromise(64)
    .then((buf) => {
        console.log(buf.toString('base64'));
    })
    .catch((error) => {
        console.error(error);
    })
async function randomStrFunction(num) {
    const randomStr = await randomBytesPromise(num);
    console.log(randomStr.toString('base64'));
}

console.log(randomStrFunction(64));


   >> deprecated란 '중요도가 떨어져 더 이상 사용되지 않고 앞으로는 사라지게 될' 것이라는 뜻.
   >> 각종 편의기능을 모아둔 모듈이며 deprecated와 promisify가 자주 쓰인다.
   >> util.deprecate : 함수가 deprecated 처리되었음을 알려줄 수 있다.
                       함수가 조만간 사라지거나 변경될 것을 알려줄 수 있어 유용하다
   >> util.promisify : 콜백 패턴을 프로미스 패턴으로 바꿔준다.
    - 바꿀 함수를 인자로 제공하면 된다.  프로미스이므로 async/await 패턴까지 사용할 수 있다.
     단, 콜백이(error,data) => {} 형식이여야 한다.
    - randomBytes에는 프로미스를 콜백으로 바꾸는 util.callbackity도 있지만 잘 사용되질 않는다.


worker_thread
최종 예시는 다시 한번 복습이 필요하다.

const { Worker, isMainThread, parentPort } = require('worker_threads');

// 처음에는 메인스레드가 실행되서 내부에서 워커스레드를 생성해
// 워커스레드에 일을 분배한다. 일 처리가 끝나면 다시 메인스레드로 보내
// 메인스레드 내에서 워커스레드 내의 일을 합쳐 최종적인 결과물로 리턴한다.
if(isMainThread) { // 메인스레드
    const worker = new Worker(__filename);
    worker.on('message', (value) => console.log('워커로부터', value));
    worker.on('exit', () => console.log('워커 끝'));
    worker.postMessage('ping');
} else { // 워커스레드
    parentPort.on('message', (value) => {
        console.log('부모로부터', value);
        parentPort.postMessage('pong');
        parentPort.close();
    });
}

// 처음에는 메인스레드가 실행되서 내부에서 워커스레드를 생성해
// 워커스레드에 일을 분배한다. 일 처리가 끝나면 다시 메인스레드로 보내
// 메인스레드 내에서 워커스레드 내의 일을 합쳐 최종적인 결과물로 리턴한다.



const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

if(isMainThread) { // 메인스레드
    const threads = new Set();
    threads.add(new Worker(__filename, {
        workerData : {start : 1},
    }));

    threads.add(new Worker(__filename, {
        workerData : {start : 2},
    }));

    for (let worker of threads) {
        worker.on('message', (value) => console.log('워커로부터', value));
        worker.on('exit', () => {
            threads.delete(worker);
            if(threads.size === 0) {
                console.log('워커 끝');
            }
        });
    }
} else { // 워커스레드
    const data = workerData;
    parentPort.postMessage(data.start + 100);
}




child_process

// exec.js

const exec = require('child_process').exec;

var process = exec('dir');

process.stdout.on('data', (data) => {
    console.log(data.toString('utf8'));
});
process.stderr.on('data', (data) => {
    console.error(data.toString('utf8'));
});

// Node에서 파이썬을 스폰으로 호출할 수 있다.
// 파이썬을 직접 실행하는게 아니라 파이썬에게 실행해달라 요청하는 것이다.
const spawn = require('child_process').spawn;
const spawnProcess = spawn('python', ['test.py']);

spawnProcess.stdout.on('data', (data) => {
    console.log(data.toString());
});
spawnProcess.stderr.on('data', (data) => {
    console.error(data.toString());
});


 
3.6 파일 시스템 접근하기
 1. fs
  >> 파일 시스템에 접근하는 모듈
   - 파일/폴더 생성, 삭제, 읽기 쓰기가 가능하다.
   - 웹 브라우저에서는 제한적이었으나 노드는 권한을 가지고 있다.

// readFile.js

const fs = require('fs');

fs.readFile('./readme.txt', (err,data) => {
    if(err) {
        throw err;
    }
    console.log(data.toString());
});

// await도 사용이 가능하다.
const fsPromise = require('fs').promises;
fsPromise.readFile('./readme.txt')
    .then((data) => {
        console.log(data.toString());
    })
    .catch((err) => {
        console.error(err);
    });
async function readFunction() {
    const readme = await fsPromise.readFile('./readme.txt');
    return readme.toString();
}

 

// writeFile.js

const fsPromise = require('fs').promises;
fsPromise.writeFile('./writeFile.txt','글이 입력됩니다.')
    .then(() => {     // 쓰기
        return fsPromise.readFile('./writeFile.txt')
    })
    .then((data) => { // 읽기
        console.log(data.toString());
    })
    .catch((err) => {
        console.error(err);
    });


  
 5. 동기 메서드와 비동기 메서드
   >> 동기메서드

const fs = require('fs');
// 동기이기 때문에 순차적으로 실행이 된다.
// 단한번 실행하거나 서버 초기화할때 동기를 사용한다.
// 서버실행 이후에는 동기코드를 사용하면 다른 사람들이 기다려야 한다.
// 그러므로 동기작업은 사용 시 늘 주의해야 한다.
let data = fs.readFileSync('./readme.txt');
console.log('1번', data.toString());
data = fs.readFileSync('./readme.txt');
console.log('2번', data.toString());
data = fs.readFileSync('./readme.txt');
console.log('3번', data.toString());
data = fs.readFileSync('./readme.txt');
console.log('4번', data.toString());


  >> 비동기메서드

// async.js

const fs = require('fs');
// 비동기 함수이기 때문에 콜백들을 백그라운드로 보내준다.
// 백그라운드로 넘어가면 동시에 수행되기 때문에 매번 순서가 바뀐다.
// 순서보장이 안된다.
fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log('1번', data.toString());
});
fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log('2번', data.toString());
});
fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log('3번', data.toString());
});
fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log('4번', data.toString());
});

 

// asyncOrder.js

const fs = require('fs');
// 비동기일때 콜백으로 다음순서를 넣어준다.
// 이렇게 되면 동기와 다를바가 없어 보일수도 있다.
// 하지만, asyncOrder.js파일을 실행할 때에는 각각의 파일이 비동기로 수행된다.
// 모든 파일들이 백 그라운드로 들어간다고 보면 된다.
// 방금 전 sync.js파일을 여러개 실행할 때에는 앞에 파일이 모든 실행이 끝나기 전에는 다음 파일을 실행할 수 없다.

// 콜백헬...
fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log('1번', data.toString());
    fs.readFile('./readme.txt', (err, data) => {
        if (err) {
            throw err;
        }
        console.log('2번', data.toString());
        fs.readFile('./readme.txt', (err, data) => {
            if (err) {
                throw err;
            }
            console.log('3번', data.toString());
            fs.readFile('./readme.txt', (err, data) => {
                if (err) {
                    throw err;
                }
                console.log('4번', data.toString());
            });
        });
    });
});
// asyncOrderPromise.js

const fs = require('fs').promises;
// fs.readFile('./readme.txt')
//     .then((data) => {
//         console.log('1번', data.toString());
//         return fs.readFile('./readme.txt');
//     })
//     .then((data) => {
//         console.log('2번', data.toString());
//         return fs.readFile('./readme.txt');
//     })
//     .then((data) => {
//         console.log('3번', data.toString());
//         return fs.readFile('./readme.txt');
//     })
//     .then((data) => {
//         console.log('4번', data.toString());
//         return fs.readFile('./readme.txt');
//     })
//     .catch((error) => console.log(err));

async function main() {
    let data = await fs.readFile('./readme.txt');
    console.log('1번', data.toString());
    data = await fs.readFile('./readme.txt');
    console.log('2번', data.toString());
    data = await fs.readFile('./readme.txt');
    console.log('3번', data.toString());
    data = await fs.readFile('./readme.txt');
    console.log('4번', data.toString());
}

main();



  9. 버퍼와 스트림 이해하기
   >> 버퍼 : 일정한 크기로 모아두는 데이터
    - 일정한 크기가 되면 한 번에 처리한다.
    - 버퍼링 : 버퍼에 데이터가 찰 때까지 모으는 작업이다.

const buffer = Buffer.from('저를 버퍼로 바꿔주세요');
console.log(buffer);           
console.log(buffer.length);
console.log(buffer.toString());


// 버퍼가 여러개 쌓이게 되면 하나의 버퍼로 합칠 수 있다.
const array = [Buffer.from('띄엄 '), Buffer.from('띄엄 '), Buffer.from('띄어쓰기 ')];
console.log(Buffer.concat(array).toString());

// Buffer에 아무것도 안들어있는데 5byte짜리 데이터를 널을 수도 있다.
console.log(Buffer.alloc(5));


   >> 스트림 : 데이터의 흐름
    - 일정한 크기로 나눠서 여러 번에 걸쳐서 처리한다.
    - 버퍼(또는 청크)의 크기를 작게 만들어서 주기적으로 데이터를 전달한다.
    - 스트리밍 : 일정한 크기의 데이터를 지속적으로 전달하는 작업이다.

// createReadStream.js

const fs = require('fs');

// createReadStream은 최초 64kbyte를 읽어온다.
// highWaterMark 속성을 통해 16byte씩 갖고오도록 변경 처리
// 스트림관리가 메모리 관리에서 가장 효율적이다.
const readStream = fs.createReadStream('./readme3.txt', {highWaterMark: 16});
const data = [];

readStream.on('data', (chunk) => {
    data.push(chunk);
    console.log('data: ',chunk, chunk.length);
});
//  data:  <Buffer eb 82 98 eb 8a 94 20 ea b8 b8 ea b2 8c 20 ec a0> 16
//  data:  <Buffer 81 ec 9c bc eb a0 a4 ea b3 a0 20 ed 95 9c eb 8b> 16
//  data:  <Buffer a4 2e 0d 0a ec a2 80 eb 8d 94 20 ea b8 b8 ea b2> 16
//  data:  <Buffer 8c 20 ec a0 81 ec 96 b4 eb b3 b4 ec 9e 90 2e 0d> 16
//  data:  <Buffer 0a ec a2 80 eb 8d 94 20 ea b8 b8 ea b2 8c 2e 2e> 16
//  data:  <Buffer ea b8 b8 ea b2 8c 2e 2e 2e 20 ed 95 98 2e 2e 2e> 16
//  data:  <Buffer 20 ed 9e 98 eb 93 a4 ec 96 b4> 10

readStream.on('end', () => {
    console.log('end: ',Buffer.concat(data).toString());
//  end:  나는 길게 적으려고 한다.
//  좀더 길게 적어보자.
//  좀더 길게..길게... 하... 힘들어
});

// 비동기처리는 항상 error처리를 해줘야 한다.
readStream.on('error', err => console.log(err));

 

  >> 파일을 스트림으로 만드는 방법

// createWriteStream.js

const fs = require('fs');

const writeStream = fs.createWriteStream('./writeme2.txt');
writeStream.on('finish', () => {
    console.log('작성완료');
});

writeStream.write('이글을쓴다.\n');
writeStream.write('이글을쓴다.... 끝...');
writeStream.end();

 

  >> 커다란 파일을 만들어서 버퍼와 스트림을 사용하여 메모리 사용량을 비교한다.

   - 하단의 결과를 확인해보면 스트림이 메모리 사용에 가장 효율적인 것을 확인할 수 있다.

// createBicFile.js

const fs = require('fs');
const file = fs.createWriteStream('./bic.txt');

for(let i = 0; i <= 10_000_000; i++) {
    file.write('안녕하세요. 데이터 용량이 큰 파일을 만들면서 메모리 체크를 해볼 예정입니다.');
}
file.end();
// buffer-memory.js


const fs = require('fs');
console.log('before', process.memoryUsage().rss);
const data1 = fs.readFileSync('./bic.txt');
fs.writeFileSync('./big2.txt', data1);
console.log('buffer: ',process.memoryUsage().rss);
// 메모리 사용량
// before : 26185728
// buffer : 1097293824
// stream-memory.js

const fs = require('fs');
console.log('before', process.memoryUsage().rss);

const readStream = fs.createReadStream('./bic.txt');
const writeStream = fs.createWriteStream('./bic3.txt');
readStream.pipe(writeStream);
readStream.on('end', () => {
    console.log('stream : ', process.memoryUsage().rss);
})
// 메모리 사용량
// before : 26181632
// stream : 46559232

  


  >> 파이프 : 파이프를 지원하는(스트림을 지원하는) 애들끼리만 가능하다.

const fs = require('fs');
const zlib = require('zlib');

// readme3.txt를 16byte씩 읽어 writeme3.txt에 16byte씩 저장한다.
// 스트림을 하면 압축도 할 수 있고 다양한 스트림을 조합할 수 있다.
const readStream = fs.createReadStream('./readme3.txt',{highWaterMark:16});
const zlibStream = zlib.createGzip();
const writeStream = fs.createWriteStream('./writeme3.txt')
readStream.pipe(zlibStream).pipe(writeStream);

 

 18. 기타 fs 메서드

  >> 파일 및 폴더 생성

// fsCreate.js

const fs = require('fs').promises;
const constants = require('fs').constants;

// F_OK : 파일 존재여부, W_OK : 쓰기 권한여부, R_OK : 읽기 권한 여부
fs.access('./folder', constants.F_OK | constants.W_OK | constants.R_OK)
    .then(() => {
        return Promise.reject('이미 폴더가 존재한다.');
    })
    .catch((err) => {
        if(err.code === 'ENOENT') {
            console.log('폴더가 없다.');
            return fs.mkdir('./folder');
        }
        return Promise.reject(err);
    })
    .then(() => {
        console.log('폴더 만들기 성공');
        return fs.open('./folder/file.js', 'w');
    })
    .then(() => {
        console.log('빈 파일 만들기 성공', fs);
        return fs.rename('./folder/file.js', '/folder/newfile.js');
    })
    .then(() => {
        console.log('이름 바꾸기 성공');
    });

 

  20. 폴더 내용 확인 및 삭제

// fsDelete.js

const fs = require('fs').promises;

fs.readdir('./folder')
    .then((dir) => {
        console.log('폴더 내용 확인', dir);

        // unlink는 파일을 삭제한다.
        return fs.unlink('./folder/newFile.js');
    })
    .then(() => {
        console.log('파일 삭제 성공');
        // rmdir은 폴더를 삭제한다.
        return fs.rmdir('./folder');
    })
    .then(() => {
        console.log('폴더 삭제 성공');
    })
    .catch((err) => console.log(err));

 

  21. 파일을 복사하는 방법

   - createReadStream과 createWriteStream끼리 연결하여 복사하는 방법 대신 사용할 수 있다.

// copyFile.js

const fs = requrie('fs').promises;

fs.copyFile('readme4.txt', 'writeme4.txt')
    .then(() => {
        console.log('복사 완료');
    })
    .catch((err) => console.log(err));

 

  22. 파일을 감시하는 방법

   - 파일의 변경사항 발생 시 이벤트가 호출된다. (change : 내용물의 수정, rename : 파일몇 변경 또는 파일 삭제 후)

// watch.js
// 파일을 감시하는 방법(변경사항 발생 시 이벤트가 호출된다.)
const fs = require('fs');

fs.watch('./target.txt',(eventType, fileName) => {
    console.log(eventType, fileName);
});

// 내용물 수정시 change target.txt
// 파일명 변경 또는 파일 삭제 후 rename target.txt

 

 23. 스레드풀 알아보기

  >> fs, crypto, zlib 모듈의 메서드를 실행할 때는 백그라운드에서 동시에 실행된다.

   - 스레드풀이 동시에 처리해 준다. 

// threadpool.js

const crypto = require('crypto');

const pass = 'pass';
const salt = 'salt';
const start = Date.now();

// 로그를 확인해보면 기본적으로 동시에 4개씩 실행이 된다.
// 자신의 사양에 맞춰 쓰레드 숫자를 변경할 수도 있다.
// cmd 명령어 : SET(window만) UV_THREADPOOL_SIZE=8 
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
    console.log('1', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
    console.log('2', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
    console.log('3', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
    console.log('4', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
    console.log('5', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
    console.log('6', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
    console.log('7', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
    console.log('8', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
    console.log('9', Date.now() - start);
});

 

3.7 이벤트 이해하기

 2. 커스텀 이벤트 예제

// event.js

const EventEmitter = require('events');

const myEvent = new EventEmitter();
myEvent.addListener('event1', () => {
    console.log('이벤트1');
});

myEvent.on('event2', () => {
    console.log('이벤트2');
});
myEvent.on('event2', () => {
    console.log('이벤트2 추가');
});

myEvent.once('event3',() => {
    console.log('이벤트3 : 나는 한번만 실행된다!');
});

myEvent.emit('event1');   // 이벤트1
myEvent.emit('event2');   // 이벤트2
                          // 이벤트2 추가
myEvent.emit('event3');   // '나는 한번만 실행된다'
myEvent.emit('event3');   // 

myEvent.on('event4', () => {
    console.log('이벤트 4');
});

myEvent.removeAllListeners('event4');  // 이벤트 삭제(하위 이벤트들도 삭제됨)
myEvent.emit('event4');                // 

const listener = () => {
    console.log('이벤트 5');
};
myEvent.on('event5', listener);
myEvent.removeListener('event5', listener);
myEvent.emit('event5');

 

 3.8 예외 처리하기

  1. 예외 처리

   >> 예외(Exception) : 처리하지 못한 에러

    - 노드 스레드를 멈춤

    - 노드는 기본적으로 싱글 스레드라 스레드가 멈추면 프로세스가 멈춘다.

    - 그러므로 에러 처리는 필수이다.

// error1.js

setInterval(() => {
    console.log('시작');
    try {
        throw new Error('서버를 고장내주마!');
    } catch (err) {
        console.error(err);
    }
}, 1000);

  - 해당 에러 발생 시 try catch를 처리해주지 않았으면 쓰레드가 멈춰 프로세스가 멈춘다.

  - 또한 try ~ catch로 감싸지 않아도 되는 부분들이 존재하는데 promise나 callback에 error값을 갖고 있을 때이다.

  - 노드가 기본적으로 제공하는 비동기 메서드들의 callback error는 노드 프로세스를 멈추지 않는다.

 

// error2.js
// 에러가 발생해도 프로세스가 멈추지 않는 예

const fs = require('fs');
setInterval(() => {
    fs.unlink('./abcdefg.js', (err) => {
        if(err) {
            console.log(err);
        }
    });
},1000);

 

  >> 프로미스의 에러는 따로 처리하지 않아도 된다.

   - 버전이 올라가면 바뀔수 있으니 주의(v21.4.0에서는 프로세스가 죽는 것 같다??)

    학습하면서 좀더 확인해보자..

const fs = require('fs').promises;

setInterval(() => {
    fs.unlink('./abcdefg.js');
}, 1000);

//  node:internal/fs/promises:1059
//  return await PromisePrototypeThen(
//           ^

//  Error: ENOENT: no such file or directory, unlink 'C:\nodejs\abcdefg.js'
//      at async Object.unlink (node:internal/fs/promises:1059:10) {
//    errno: -4058,
//    code: 'ENOENT',
//    syscall: 'unlink',
//    path: 'C:\\nodejs\\abcdefg.js'
//   }
//  Node.js v21.4.0

 

  >> 최후의 수단으로 사용

   - 콜백 함수의 동작이 보장되지 않는다. 즉 복구 작업용으로 사용하는건 부적합 하다는 의미이다.

   - 따라서 에러 내용 기록 용으로만 쓰는 게 좋다.

process.on('uncaughtException', (err) => console.error('예기치 못한 에러',err));

setInterval(() => {
    throw new Error('서버를 고장내주마!!');
},1000);

setTimeout(() => {
    console.log('실행됩니다.');
},2000);

// 예기치 못한 에러 Error: 서버를 고장내주마!!
//     at Timeout._onTimeout (C:\nodejs\error4.js:4:11)
//     at listOnTimeout (node:internal/timers:573:17)
//     at process.processTimers (node:internal/timers:514:7)
// 실행됩니다.
// 예기치 못한 에러 Error: 서버를 고장내주마!!
//     at Timeout._onTimeout (C:\nodejs\error4.js:4:11)
//     at listOnTimeout (node:internal/timers:573:17)
//     at process.processTimers (node:internal/timers:514:7)
// 반복...

  

 6. 프로세스 종료하기

  >> 윈도우

   $ netstat -ano | findstr 포트

   $ taskkill /pid 프로세스아이디 /f

  >> 맥/리눅스

   $ lsof -i  tcp:포트

   $ kill -9 프로세스아이디