본문 바로가기

WINK-(Web & App)/Express.js (Node.js) 스터디

[2024 여름방학 Node.js 스터디] 백채린 #2주차 3장

반응형

3장 노드 기능 알아보기

 

 

3.1 REPL 사용하기

 

- REPL

: 입력한 코드를 읽고(Read), 해석하고(Eval), 결과물을 반환하고(Pring), 종료할 때까지 반복한다(Loop)

노드의 REPL 사용

 

- REPL은 한두 줄짜리 코드를 테스트해보는 용도로는 좋지만 여러 줄의 코드를 실행하기에는 불편함

 

 

3.2 JS 파일 실행하기

 

js 파일 생성 후 실행하기

 

3.3 모듈로 만들기

 

 

 

 

- 모듈: 특정한 기능을 하는 함수나 변수들의 집합

- 노드는 코드를 모듈로 만들 수 있음

- 보통 파일 하나가 모듈 하나가 되며, 파일별로 코드를 모듈화

- 노드에서는 CommonJS, ECMAScrips 2가지 모듈 사용

 

 

 

 

3.3.1 CommonJS 모듈

 

- CommonJS 모듈: 노드 생태계에서 가장 널리 쓰이는 모듈

 

모듈 하나가 여러 개의 모듈을 사용

 

 

 

- modult.exports와 exports가 같은 객체를 참조

exports.odd = 'CJS 홀수입니다';
exports.even = 'CJS 짝수입니다';

// 콘솔
$ node index
CJS 짝수입니다
CJS 홀수입니다

 

 

!주의! exports는 객체만 사용할 수 있으므로 func.js와 같이 module.exports에 함수를 대입한 경우에는 exports로 바꿀 수 없음

exports module.exports에는 참조 관계가 있으므로 한 모듈에 exports 객체와 module.exports를 동시에 사용하지 않는 것이 좋음

 

- require: 모듈을 불러옴, 함수(객체)

require.cache와 require.main

require가 반드시 파일 최상단에 위치할 필요가 없고, module.exports도 최하단에 위치할 필요가 없음

 한번 require한 파일은 require.cache에 저장됨, 다음 번에 require할 때는 새로 불러오지 않고 require.cache에 있는 것 재사용

require.main은 노드 실행 시 첫 모듈을 가리킴

현재 파일이 첫 모듈인지 알아보려면 require.main === module 해보면 됨

 

- 순환 참조

(circular dependency)

순환 참조되는 대상을 빈 객체로 만듦

조용히 빈 객체로 변경되므로 예기치 못한 동작이 발생, 따라서 순환 참조가 발생하지 않도록 구조를 잘 잡는 것이 중요

 

 

3.3.2 ECMAScript 모듈

 

- ECMAScript 모듈: 공식적인 자바스크립트 모듈 형식

- 장점: 브라우저에서도 ES 모듈을 사용할 수 있어 브라우저와 노드 모두에 같은 모듈 형식을 사용할 수 있음

 

ES 모듈 스타일

 require - import,  exports - export, module.exports - export default

 ES 모듈의 import export default require module처럼 함수나 객체가 아니라 문법 그 자체임

 파일도 js 대신 mjs 확장자로 변경,

     js 확장자에서 import를 사용하면 SyntaxError: Cannot use import statement outside a module 에러가 발생

 CommonJS 모듈과는 다르게 import 시 파일 경로에서 js, mjs 같은 확장자는 생략 불가능, 폴더 내부에서 또한 index.js 생략 불가능

 

- CommonJS 모듈과 ECMAScript 모듈의 차이 (웬만하면 한 가지 형식만 사용하는 것을 권장)

차이점 CommonJS 모듈 ECMAScript 모듈
문법 require('./a');
module.exports = A;
const A = require('./a');
exports.C = D;
const E = F; exports.E = E;
const { C, E } = require ('./b');
import './a.mjs';
export default A;
import A from './a.mjs';
export const C = D;
const E = F; export { E };
import { C, E } from './b.mjs';
확장자 js
cjs
js(package.json에 type: "module" 필요)
mjs
확장자 생략 가능 불가능
다이내믹 임포트 가능(3.3.3절 참고) 불가능
인덱스(index) 생략 가능(require('./folder')) 불가능(import './folder/index.mjs')
top level await 불가능 가능
__filename, __dirname, require, module.exports, exports 사용 가능(3.3.4절 참고) 사용 불가능(__filename 대신 import.meta.url 사용)
서로 간 호출 가능

 

 

3.3.3 다이내믹 임포트

 

- 다이내믹 임포트: 조건부로 모듈을 불러오는 것

 

- ES 모듈

if 문 안에서  import 하는 것이 불가능 → import라는 함수를 사용해서 모듈을 동적으로 불러옴

import Promise를 반환하기에 await이나 then을 붙여야 함

ES 모듈의 최상위 스코프에서는 async 함수 없이도 await 가능, CommonJS 모듈에서는 불가능

export default의 경우 import할 때도 default라는 속성 이름으로 import 됨, CommonJS 모듈에서 module.exports한 것도 default라는 이름으로 import 됨

 

 

3.3.4 __filename, __dirname

 

- 노드는 __filename, __dirname이라는 키워드로 경로에 대한 정보를 제공

- 파일에 __filename __dirname을 넣어두면 실행 시 현재 파일명과 현재 파일 경로로 바뀜

- ES 모듈에서는 __filename __dirname 사용 X, 대신 import.meta.url로 경로를 가져올 수 있음

CommonJS 모듈
ES 모듈

 

 

3.4 노드 내장 객체 알아보기

 

 

3.4.1 global

 

- global 객체: 전역 객체로 모든 파일에서 접근 가능, 생략도 가능

* console 객체도 원래는 global.console 이다.

// globalA.js
module.exports = () => global.message;

// globalB.js
const A = require('./globalA');

global.message = '안녕하세요';
console.log(A());

// 콘솔
$ node globalB
안녕하세요

 

→ globalB에서 넣은 global.message 값을 globalA에서도 접근

 

!주의! 프로그램의 규모가 커질수록 어떤 파일에서 global 객체에 값을 대입했는지 찾기 힘들어 유지 보수에 어려움을 겪게 되기 때문에

남용하지 않는 것이 좋음

 

 

3.4.2 console

 

- console 객체

: 디버깅, 개발 중 변수에 값이 제대로 들어 있는지 확인, 에러 발생 시 에러 내용을 콘솔에 표시, 코드 실행 시간을 알아보기 위해 사용

  대표적으로 console.log 메서드가 있음

 

  • console.time(레이블): console.timeEnd(레이블)과 대응되어 같은 레이블을 가진 time timeEnd 사이의 시간을 측정합니다.
  • console.log(내용): 평범한 로그를 콘솔에 표시합니다. console.log(내용, 내용, …)처럼 여러 내용을 동시에 표시할 수도 있습니다.
  • console.error(에러 내용): 에러를 콘솔에 표시합니다.
  • console.table(배열): 배열의 요소로 객체 리터럴을 넣으면, 객체의 속성들이 테이블 형식으로 표현됩니다. 
  • console.dir(객체, 옵션): 객체를 콘솔에 표시할 때 사용합니다. 첫 번째 인수로 표시할 객체를 넣고, 두 번째 인수로 옵션을 넣습니다. 옵션의 colors true로 하면 콘솔에 색이 추가되어 보기가 한결 편해집니다. depth는 객체 안의 객체를 몇 단계까지 보여줄지를 결정합니다. 기본값은 2입니다.
  • console.trace(레이블): 에러가 어디서 발생했는지 추적할 수 있게 합니다. 보통은 에러 발생 시 에러 위치를 알려주므로 자주 사용하지 않지만, 위치가 나오지 않는다면 사용할 만합니다.

 

 

3.4.3 타이머

 

- 타이머 기능을 제공하는 함수

: setTimeout, setInterval, setImmediate (노드에서 global 객체 안에 들어 있음)

 

  • setTimeout(콜백 함수, 밀리초): 주어진 밀리초(1,000분의 1초) 이후에 콜백 함수를 실행합니다.
  • setInterval(콜백 함수, 밀리초): 주어진 밀리초마다 콜백 함수를 반복 실행합니다.
  • setImmediate(콜백 함수): 콜백 함수를 즉시 실행합니다.

- 아이디를 사용하여 타이머 취소

 

  • clearTimeout(아이디): setTimeout을 취소합니다.
  • clearInterval(아이디): setInterval을 취소합니다.
  • clearImmediate(아이디): setImmediate를 취소합니다.

- 프로미스 기반 타이머 (타이머는 콜백 기반 API)

awai을 사용하기 위해 ES 모듈 사용



3.4.4 process

 

- process 객체: 현재 실행되고 있는 노드 프로세스에 대한 정보를 담고 있음

 

$ node
> process.version
v18.7.0 // 설치된 노드의 버전입니다
> process.arch
x64 // 프로세서 아키텍처 정보입니다. arm, ia32 등의 값일 수도 있습니다
> process.platform
win32 // 운영체제 플랫폼 정보입니다. linux나 darwin, freebsd 등의 값일 수도 있습니다
> process.pid
14736 // 현재 프로세스의 아이디입니다. 프로세스를 여러 개 가질 때 구분할 수 있습니다
> process.uptime()
199.36 // 프로세스가 시작된 후 흐른 시간입니다. 단위는 초입니다
> process.execPath
C:\Program Files\nodejs\node.exe // 노드의 경로입니다
> process.cwd()
C:\Users\zerocho // 현재 프로세스가 실행되는 위치입니다
> process.cpuUsage()
{ user: 390000, system: 203000 } // 현재 cpu 사용량입니다

 

3.4.4.1 process.env

 

- process.env

: 시스템의 환경 변수 출력,

  대표적인 것으로 UV_THREADPOOL_SIZE NODE_OPTIONS가 있음

 

- NODE_OPTIONS: 노드를 실행할 때의 옵션들을 입력받는 환경 변수

- UV_THREADPOOL_SIZE: 노드에서 기본적으로 사용하는 스레드 풀의 스레드 개수를 조절할 수 있게 함

 

NODE_OPTIONS=--max-old-space-size=8192 
// 왼쪽: 환경 변수 이름, 오른쪽: 값
// --max-old-space-size=8192는 노드의 메모리를 8GB까지 사용할 수 있게 함
UV_THREADPOOL_SIZE=8

 

- process.env는 서비스의 중요한 키를 저장하는 공간으로도 사용하지만 서버나 데이터베이스의 비밀번호와 각종 API 키를 코드에 직접 입력하는 것은 위험 (서비스가 해킹당해 코드가 유출될 경우, 비밀번호가 코드에 남아 있기 때문에)

중요한 비밀번호는 process.env의 속성으로 대체

 

const secretId = process.env.SECRET_ID;
const secretCode = process.env.SECRET_CODE;
// 이제 process.env에 직접 SECRET_ID와 SECRET_CODE를 넣으면 됨

 

3.4.4.2 process.nextTick(콜백)

 

- 이벤트 루프가 다른 콜백 함수들보다 nextTick의 콜백 함수를 우선으로 처리하도록 만듦

 

process.nextTick 은  setImmediate 나  setTimeout 보다 먼저 실행

→ resolve Promise nextTick처럼 다른 콜백들보다 우선시

마이크로태스크: process.nextTick, Promise

 

 

 

 

 

 

!주의!

마이크로태스크를 재귀 호출하게 되면 이벤트 루프는

다른 콜백 함수보다 마이크로태스크를 우선해 처리하므로

콜백 함수들이 실행되지 않을 수도 있음

 

 

 

 

 

 

3.4.4.3 process.exit(코드)

 

- process.exit(코드): 실행 중인 노드 프로세스를 종료

- 이 함수를 사용하면 서버가 멈추므로 서버에서 잘 사용하지 않지만 서버 외의 독립적인 프로그램에서는 수동으로 노드를 멈추기 위해 사용

 

// i가 5가 되었을 때 종료
let i = 1;
setInterval(() => {
  if (i === 5) {
    console.log('종료!');
    process.exit();
  }
  console.log(i);
  i += 1;
}, 1000);

 

- process.exit 메서드는 인수를 주지 않거나 0을 주면 정상 종료를 뜻하고, 1을 주면 비정상 종료를 뜻함

 

 

3.4.5 기타 내장 객체

 

  • URL, URLSearchParams: 3.5.3절에서 다룹니다.
  • AbortController, FormData, fetch, Headers, Request, Response, Event, EventTarget: 브라우저에서 사용하던 API가 노드에도 동일하게 생성되었습니다.
  • TextDecoder: Buffer를 문자열로 바꿉니다.
  • TextEncoder: 문자열을 Buffer로 바꿉니다.
  • WebAssembly: 웹어셈블리 처리를 담당합니다.

 

 

3.5 노드 내장 모듈 사용하기

 

 

3.5.1 os

 

- 내장 모듈인 os를 불러오려면 require('os') 또는 require('node:os')를 하면 됨

 

  • os.arch(): process.arch와 동일합니다.
  • os.platform(): process.platform과 동일합니다.
  • os.type(): 운영체제의 종류를 보여줍니다.
  • os.uptime(): 운영체제 부팅 이후 흐른 시간(초)을 보여줍니다. process.uptime()은 노드의 실행 시간이었습니다.
  • os.hostname(): 컴퓨터의 이름을 보여줍니다.
  • os.release(): 운영체제의 버전을 보여줍니다.
  • os.homedir(): 홈 디렉터리 경로를 보여줍니다.
  • os.tmpdir(): 임시 파일 저장 경로를 보여줍니다.
  • os.cpus(): 컴퓨터의 코어 정보를 보여줍니다.
  • os.freemem(): 사용 가능한 메모리(RAM)를 보여줍니다.
  • os.totalmem(): 전체 메모리 용량을 보여줍니다.

* os.constants: 각종 에러와 신호에 대한 정보가 담겨 있음

 

 

3.5.2 path

 

- path

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

  운영체제별로 경로 구분자가 다르기 때문에 필요

  크게 윈도 타입과 POSIX(유닉스 기반의 운영체제로 맥과 리눅스) 타입으로 구분

 

  • 윈도: C:\Users\ZeroCho처럼 \로 구분합니다.
  • POSIX: /home/zerocho처럼 /로 구분합니다.

 

  • path.sep: 경로의 구분자입니다. 윈도는 \, POSIX는 /입니다.
  • path.delimiter: 환경 변수의 구분자입니다. process.env.PATH를 입력하면 여러 개의 경로가 이 구분자로 구분되어 있습니다. 윈도는 세미콜론(;)이고, POSIX는 콜론(:)입니다.
  • path.dirname(경로): 파일이 위치한 폴더 경로를 보여줍니다.
  • path.extname(경로): 파일의 확장자를 보여줍니다.
  • path.basename(경로, 확장자): 파일의 이름(확장자 포함)을 표시합니다. 파일의 이름만 표시하고 싶다면 basename의 두 번째 인수로 파일의 확장자를 넣으면 됩니다.
  • path.parse(경로): 파일 경로를 root, dir, base, ext, name으로 분리합니다.
  • path.format(객체): path.parse()한 객체를 파일 경로로 합칩니다.
  • path.normalize(경로): /나 \를 실수로 여러 번 사용했거나 혼용했을 때 정상적인 경로로 변환합니다.
  • path.isAbsolute(경로): 파일의 경로가 절대경로인지 상대경로인지를 true false로 알립니다.
  • path.relative(기준경로, 비교경로): 경로를 두 개 넣으면 첫 번째 경로에서 두 번째 경로로 가는 방법을 알립니다.
  • path.join(경로, …): 여러 인수를 넣으면 하나의 경로로 합칩니다. 상대경로인 ..(부모 디렉터리)과 .(현 위치)도 알아서 처리합니다.
  • path.resolve(경로, …): path.join()과 비슷하지만 차이가 있습니다. 차이점은 다음에 나오는 Note에서 설명합니다.
 

- 가끔 윈도에서 POSIX 스타일 경로를 사용할 때가 있고, 그 반대일 때도 있는데

   이러한 경우 윈도에서는 path.posix.sep이나 path.posix.join()과 같이 사용

   POSIX에서는 path.win32.sep이나 path.win32.join()과 같이 사용

 

- 노드는 require.main 파일을 기준으로 상대경로를 인식, 따라서 require.main과는 다른 디렉터리의 파일이 상대경로를 갖고 있다면

   예상과 다르게 동작할 수 있으며 이 문제는 path 모듈을 통해 해결

 

 

3.5.3 url

 

- url

: 인터넷 주소를 쉽게 조작하도록 도와주는 모듈

  크게 2가지 방식으로 WHATWG(웹 표준을 정하는 단체의 이름) 방식의 url, 예전부터 노드에서 사용하던 방식의 url

  요즘은 WHATWG 방식만 사용

 

WHATWG와 노드의 주소 체계

 

url.format(객체) : 분해되었던  url  객체를 다시 원래 상태로 조립

→ search 부분(쿼리스트링)은 보통 주소를 통해 데이터를 전달할 때 사용,

     search는 물음표(?)로 시작하고, 그 뒤에 키=값 형식으로 데이터를 전달하며 여러 키가 있을 경우에는 &로 구분

     search 부분을 다루기 위해 searchParams라는 특수한 객체가 생성됨

 

URL과  URLSearchParams  모두 노드 내장 객체이므로 이번에는  require('url') 을 생략

 

URL 생성자를 통해 myURL이라는 주소 객체를 만듦, 

myURL 안에는 searchParams 객체로 search 부분을 조작하는 다양한 메서드를 지원

myURL.searchParams 대신 new URLSearchParams(myURL.search)로도 같은 결괏값을 얻을 수 있음

 

  • getAll(키): 키에 해당하는 모든 값을 가져옵니다. category 키에는 nodejs와 javascript라는 두 가지 값이 들어 있습니다.
  • get(키): 키에 해당하는 첫 번째 값만 가져옵니다.
  • has(키): 해당 키가 있는지 없는지를 검사합니다.
  • keys(): searchParams의 모든 키를 반복기(iterator)(ES2015 문법) 객체로 가져옵니다.
  • values(): searchParams의 모든 값을 반복기 객체로 가져옵니다.
  • append(키, 값): 해당 키를 추가합니다. 같은 키의 값이 있다면 유지하고 하나 더 추가합니다.
  • set(키, 값): append와 비슷하지만 같은 키의 값들을 모두 지우고 새로 추가합니다.
  • delete(키): 해당 키를 제거합니다.
  • toString(): 조작한 searchParams 객체를 다시 문자열로 만듭니다. 이 문자열을 search에 대입하면 주소 객체에 반영됩니다.

 

 

3.5.4 dns

 

- dns

: DNS를 다룰 때 사용하는 모듈

  주로 도메인을 통해 IP나 기타 DNS 정보를 얻고자 할 때 사용

 

- ip 주소는 간단하게 dns.lookup이나 dns.resolve(도메인)으로 얻을 수 있음

 

-  A(ipv4 주소), AAAA(ipv6 주소), NS(네임서버), SOA(도메인 정보), CNAME(별칭, 주로 www가 붙은 주소는 별칭인 경우가 많습니다), MX(메일 서버) 등은 레코드라고 부르는데, 해당 레코드에 대한 정보는 dns.resolve(도메인, 레코드 이름)으로 조회

 

 

3.5.5 crypto

 

- crypto: 다양한 방식의 암호화를 도와주는 모듈

 

3.5.5.1 단방향 암호화

 

- 단방향 암호화: 복호화할 수 없는 암호화 방식

* 복호화: 암호화된 문자열을 원래 문자열로 되돌려놓는 것

→ 단방향 암호화는 한번 암호화하면 원래 문자열을 찾을 수 없음

→ 복호화할 수 없으므로 암호화라고 표현하는 대신 해시 함수라고 부르기도 함

- 비밀번호는 보통 단방향 암호화 알고리즘을 사용해서 암호화

- 단방향 암호화 알고리즘은 주로 해시 기법을 사용

* 해시 기법: 어떠한 문자열을 고정된 길이의 다른 문자열로 바꿔버리는 방식

노드에서 해시 함수 사용

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

해시 함수

 

 

- 현재는 주로 pbkdf2나 bcrypt, scrypt라는 알고리즘으로 비밀번호를 암호화, 그중 노드에서 지원하는 것은 pbkdf2

- pbkdf2: 기존 문자열에 salt라고 불리는 문자열을 붙인 후 해시 알고리즘을 반복해서 적용하는 것

 

// pbkdf2.js
const crypto = require('crypto');

crypto.randomBytes(64, (err, buf) => {
  const salt = buf.toString('base>64');
  console.log('salt:', salt);
  crypto.pbkdf2('비밀번호', salt, 100000, 64, 'sha>512', (err, key) => {
    console.log('password:', key.toString('base>64'));
  });
});

 

 

pbkdf2

randomBytes이므로 매번 실행할 때마다 결과가 달라지기 때문에 salt를 잘 보관하고 있어야 비밀번호도 찾을 수 있음

→ pbkdf2는 간단하지만 bcrypt나 scrypt보다 취약하므로 나중에 더 나은 보안이 필요하면 bcrypt나 scrypt 방식을 사용

 

3.5.5.2 양방향 암호화

 

- 양방향 암호화

: 암호화된 문자열을 복호화할 수 있으며, 키(열쇠)라는 것이 사용 됨

  대칭형 암호화에서는 암호를 복호화하려면 암호화할 때 사용한 키와 같은 키를 사용해야 함

 

 

  • crypto.createCipheriv(알고리즘, 키, iv): 암호화 알고리즘과 키, iv를 넣습니다. 암호화 알고리즘은 aes-256-cbc를 사용했으며, 다른 알고리즘을 사용해도 됩니다. aes-256-cbc 알고리즘의 경우 키는 32바이트여야 하고, iv는 16바이트여야 합니다. iv는 암호화할 때 사용하는 초기화 벡터를 의미하지만, 이 책에서 설명하기에는 내용이 많으므로 AES 암호화를 따로 공부하는 것이 좋습니다. 사용 가능한 알고리즘 목록은 crypto.getCiphers()를 호출하면 볼 수 있습니다.
  • cipher.update(문자열, 인코딩, 출력 인코딩): 암호화할 대상과 대상의 인코딩, 출력 결과물의 인코딩을 넣습니다. 보통 문자열은 utf8 인코딩을, 암호는 base64를 많이 사용합니다.
  • cipher.final(출력 인코딩): 출력 결과물의 인코딩을 넣으면 암호화가 완료됩니다.
  • crypto.createDecipheriv(알고리즘, 키, iv): 복호화할 때 사용합니다. 암호화할 때 사용했던 알고리즘과 키, iv를 그대로 넣어야 합니다.
  • decipher.update(문자열, 인코딩, 출력 인코딩): 암호화된 문장, 그 문장의 인코딩, 복호화할 인코딩을 넣습니다. createCipheriv update()에서 utf8, base64 순으로 넣었다면 createDecipheriv update()에서는 base64, utf8 순으로 넣으면 됩니다.
  • decipher.final(출력 인코딩): 복호화 결과물의 인코딩을 넣습니다.

 

 

3.5.6 util

 

- util

: 각종 편의 기능을 모아둔 모듈

 

  • util.deprecate: 함수가 deprecated 처리되었음을 알립니다. 첫 번째 인수로 넣은 함수를 사용했을 때 경고 메시지가 출력됩니다. 두 번째 인수로 경고 메시지 내용을 넣으면 됩니다. 함수가 조만간 사라지거나 변경될 때 알려줄 수 있어 유용합니다.
  • util.promisify: 콜백 패턴을 프로미스 패턴으로 바꿉니다. 바꿀 함수를 인수로 제공하면 됩니다. 이렇게 바꿔두면 async/await 패턴까지 사용할 수 있어 좋습니다. 3.5.5.1절의 randomBytes와 비교해보세요. 프로미스를 콜백으로 바꾸는 util.callbackify도 있지만 자주 사용되지는 않습니다.

 

 

3.5.7 worker_threads

 

 

→ isMainThread를 통해 현재 코드가 메인 스레드(기존에 동작하던 싱글 스레드를 메인 스레드 또는 부모 스레드라고 부릅니다)에서 실행되는지, 아니면 우리가 생성한 워커 스레드에서 실행되는지 구분됨

→ 메인 스레드에서는 new Worker를 통해 현재 파일(__filename)을 워커 스레드에서 실행시키고 있으며, 현재 파일의 else 부분만 워커스레드에서 실행됨

→ 부모에서는 워커 생성 후 worker.postMessage로 워커에 데이터를 보낼 수 있음

→  워커는 parentPort.on('message') 이벤트 리스너로 부모로부터 메시지를 받고, parentPort.postMessage로 부모에게 메시지를 보내며 부모는 worker.on('message')로 메시지를 받음

* 메시지를 한 번만 받고 싶다면 once('message')를 사용

!주의! 워커에서 on 메서드를 사용할 때는 직접 워커를 종료해야 함

→ parentPort.close()를 하면 부모와의 연결이 종료되며 이때 worker.on('exit')이 실행됨

 

 

3.5.8 child_process

 

- child_process

: 노드에서 다른 프로그램을 실행하고 싶거나 명령어를 수행하고 싶을 때 사용하는 모듈

- 이 모듈을 통해 다른 언어의 코드(예를 들면, 파이썬)를 실행하고 결괏값을 받을 수 있음

- 이름이 child_process(자식 프로세스)인 이유는 현재 노드 프로세스 외에 새로운 프로세스를 띄워서 명령을 수행하고 노드 프로세스에 결과를 알려주기 때문

 

 

3.5.9 기타 모듈들

 

 async_hooks: 비동기 코드의 흐름을 추적할 수 있는 실험적인 모듈입니다.

 dgram: UDP와 관련된 작업을 할 때 사용합니다.

 net: HTTP보다 로우 레벨인 TCP나 IPC 통신을 할 때 사용합니다.

 perf_hooks: 성능 측정을 할 때 console.time보다 더 정교하게 측정합니다.

 querystring: URLSearchParams가 나오기 이전에 쿼리스트링을 다루기 위해 사용했던 모듈입니다. 요즘은 URLSearchParams를 사용하는 것을 권장합니다.

 string_decoder: 버퍼 데이터를 문자열로 바꾸는 데 사용합니다.

 tls: TLS와 SSL에 관련된 작업을 할 때 사용합니다.

 tty: 터미널과 관련된 작업을 할 때 사용합니다.

 v8: v8 엔진에 직접 접근할 때 사용합니다.

 vm: 가상 머신에 직접 접근할 때 사용합니다.

 wasi: 웹어셈블리를 실행할 때 사용하는 실험적인 모듈입니다.

 

 

 

3.6 파일 시스템 접근하기

 

- fs 모듈

: 파일 시스템에 접근하는 모듈, 즉, 파일을 생성하거나 삭제하고, 읽거나 쓸 수 있으며 폴더도 만들거나 지울 수도 있음


- fs는 기본적으로 콜백 형식의 모듈이므로 실무에서 사용하기가 불편함

  → fs 모듈을 프로미스 형식으로 바꿔주는 방법을 사용

 

 

3.6.1 동기 메서드와 비동기 메서드

 

 

 

 

 동기와 비동기: 백그라운드 작업 완료 확인 여부

 블로킹과 논블로킹: 함수가 바로 return되는지 여부

 

 

 

 

- Sync 메서드를 사용할 때는 이전 작업이 완료되어야 다음 작업을 진행할 수 있으므로 비효율적

- 백그라운드는 fs 작업을 동시에 처리할 수도 있는데, Sync 메서드를 사용하면 백그라운드조차 동시에 처리할 수 없게 됩니다.

  → 비동기 fs 메서드를 사용하여 백그라운드가 동시에 작업할 수도 있고, 메인 스레드는 다음 작업을 처리할 수 있도록 함

* 동기 메서드들은 이름 뒤에 Sync가 붙어 있어 구분하기 쉬움 

- 비동기 방식의 단점: 콜백 지옥 (콜백 지옥은 Promise async/await으로 어느 정도 해결 가능)

 

 

3.6.2 버퍼와 스트림 이해하기

 

- 파일을 읽거나 쓰는 방식에는 크게 두 가지 방식, 즉 버퍼를 이용하거나 스트림을 이용하는 방식이 있음

- 노드는 파일을 읽을 때 메모리에 파일 크기만큼 공간을 마련해두며 파일 데이터를 메모리에 저장한 뒤 사용자가 조작할 수 있도록 하는데, 이때 메모리에 저장된 데이터가 바로 버퍼이다.

버퍼

 

- Buffer 객체는 다음과 같은 메서드 제공

 from(문자열): 문자열을 버퍼로 바꿀 수 있습니다. length 속성은 버퍼의 크기를 알립니다. 바이트 단위입니다.

 toString(버퍼): 버퍼를 다시 문자열로 바꿀 수 있습니다. 이때 base64 hex를 인수로 넣으면 해당 인코딩으로도 변환 가능합니다.

 concat(배열): 배열 안에 든 버퍼들을 하나로 합칩니다.

 alloc(바이트): 빈 버퍼를 생성합니다. 바이트를 인수로 넣으면 해당 크기의 버퍼가 생성됩니다.

 

- 스트림: 버퍼의 크기를 작게 만들고 여러 번에 걸쳐 나눠 보내는 방식

스트림

 

 

3.6.3 기타 fs 메서드 알아보기

 

- fs: 파일 시스템을 조작하는 다양한 메서드를 제공

 

- 4가지의 비동기 메서드

 fs.access(경로, 옵션, 콜백): 폴더나 파일에 접근할 수 있는지를 체크합니다. 두 번째 인수로 상수들(constants를 통해 가져옵니다)을 넣었습니다. F_OK는 파일 존재 여부, R_OK는 읽기 권한 여부, W_OK는 쓰기 권한 여부를 체크합니다. 파일/폴더나 권한이 없다면 에러가 발생하는데, 파일/폴더가 없을 때의 에러 코드는 ENOENT입니다.

 fs.mkdir(경로, 콜백): 폴더를 만드는 메서드입니다. 이미 폴더가 있다면 에러가 발생하므로 먼저 access 메서드를 호출해서 확인하는 것이 중요합니다.

 fs.open(경로, 옵션, 콜백): 파일의 아이디(fd 변수)를 가져오는 메서드입니다. 파일이 없다면 파일을 생성한 뒤 그 아이디를 가져옵니다. 가져온 아이디를 사용해 fs.read 또는 fs.write로 읽거나 쓸 수 있습니다. 두 번째 인수로 어떤 동작을 할 것인지를 설정할 수 있습니다. 쓰려면 w, 읽으려면 r, 기존 파일에 추가하려면 a입니다. 앞의 예제에서는 w를 했으므로 파일이 없을 때 새로 만들 수 있었습니다. r이었다면 에러가 발생했을 것입니다.

 fs.rename(기존 경로, 새 경로, 콜백): 파일의 이름을 바꾸는 메서드입니다. 기존 파일 위치와 새로운 파일 위치를 적으면 됩니다. 꼭 같은 폴더를 지정할 필요는 없으므로 잘라내기 같은 기능을 할 수도 있습니다.

 

- 폴더 내용 확인 및 삭제와 관련된 메서드

 fs.readdir(경로, 콜백): 폴더 안의 내용물을 확인할 수 있습니다. 배열 안에 내부 파일과 폴더명이 나옵니다.

 fs.unlink(경로, 콜백): 파일을 지울 수 있습니다. 파일이 없다면 에러가 발생하므로 먼저 파일이 있는지를 꼭 확인해야 합니다.

 fs.rmdir(경로, 콜백): 폴더를 지울 수 있습니다. 폴더 안에 파일들이 있다면 에러가 발생하므로 먼저 내부 파일을 모두 지우고 호출해야 합니다.

 

 

3.6.4 스레드 풀 알아보기

 

- fs 메서드를 여러 번 실행해도 백그라운드에서 동시에 처리되는데, 바로 스레드 풀이 있기 때문임

 

 

 

3.7 이벤트 이해하기

 

- 이벤트 관리를 위한 메서드

 on(이벤트명, 콜백): 이벤트 이름과 이벤트 발생 시의 콜백을 연결합니다. 이렇게 연결하는 동작을 이벤트 리스닝이라고 합니다. event2처럼 이벤트 하나에 이벤트 여러 개를 달아줄 수도 있습니다.

 addListener(이벤트명, 콜백): on과 기능이 같습니다.

 emit(이벤트명): 이벤트를 호출하는 메서드입니다. 이벤트 이름을 인수로 넣으면 미리 등록해뒀던 이벤트 콜백이 실행됩니다.

 once(이벤트명, 콜백): 한 번만 실행되는 이벤트입니다. myEvent.emit('event3')을 두 번 연속 호출했지만 콜백이 한 번만 실행됩니다.

 removeAllListeners(이벤트명): 이벤트에 연결된 모든 이벤트 리스너를 제거합니다. event4가 호출되기 전에 리스너를 제거했으므로 event4의 콜백은 호출되지 않습니다.

 removeListener(이벤트명, 리스너): 이벤트에 연결된 리스너를 하나씩 제거합니다. 리스너를 넣어야 한다는 것을 잊지 마세요. 역시 event5의 콜백도 호출되지 않습니다.

 off(이벤트명, 콜백): 노드 10 버전에서 추가된 메서드로, removeListener와 기능이 같습니다.

 listenerCount(이벤트명): 현재 리스너가 몇 개 연결되어 있는지 확인합니다.

 

 

 

3.8 예외 처리하기

 

- 노드에서는 예외 처리가 정말 중요

- 멀티 스레드 프로그램에서는 스레드 하나가 멈추면 그 일을 다른 스레드가 대신하지만 노드의 메인 스레드는 하나뿐

- 에러가 발생할 것 같은 부분을 미리 try/catch로 감싸면 됨

- throw하면 노드 프로세스가 멈춰버리기 때문에 throw하는 경우에는 반드시 try/catch문으로 throw한 에러를 잡아야 함

- uncaughtException은 단순히 에러 내용을 기록하는 정도로 사용하고, 에러를 기록한 후 process.exit()으로 프로세스를 종료하는 것이 좋음

 

 

3.8.1 자주 발생하는 에러들

 

 

 

 

반응형