3장 노드 기능 알아보기
3.1 REPL 사용하기
- REPL
: 입력한 코드를 읽고(Read), 해석하고(Eval), 결과물을 반환하고(Pring), 종료할 때까지 반복한다(Loop)
- REPL은 한두 줄짜리 코드를 테스트해보는 용도로는 좋지만 여러 줄의 코드를 실행하기에는 불편함
3.2 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가 반드시 파일 최상단에 위치할 필요가 없고, module.exports도 최하단에 위치할 필요가 없음
→ 한번 require한 파일은 require.cache에 저장됨, 다음 번에 require할 때는 새로 불러오지 않고 require.cache에 있는 것 재사용
→ require.main은 노드 실행 시 첫 모듈을 가리킴
→ 현재 파일이 첫 모듈인지 알아보려면 require.main === module 해보면 됨
- 순환 참조
→ 순환 참조되는 대상을 빈 객체로 만듦
→ 조용히 빈 객체로 변경되므로 예기치 못한 동작이 발생, 따라서 순환 참조가 발생하지 않도록 구조를 잘 잡는 것이 중요
3.3.2 ECMAScript 모듈
- ECMAScript 모듈: 공식적인 자바스크립트 모듈 형식
- 장점: 브라우저에서도 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 모듈
→ 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로 경로를 가져올 수 있음
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)
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의 콜백 함수를 우선으로 처리하도록 만듦
→ 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 방식만 사용
→ search 부분(쿼리스트링)은 보통 주소를 통해 데이터를 전달할 때 사용,
search는 물음표(?)로 시작하고, 그 뒤에 키=값 형식으로 데이터를 전달하며 여러 키가 있을 경우에는 &로 구분
search 부분을 다루기 위해 searchParams라는 특수한 객체가 생성됨
→ 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'));
});
});
→ 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 자주 발생하는 에러들
'WINK-(Web & App) > Express.js (Node.js) 스터디' 카테고리의 다른 글
[2024 여름방학 Node.js 스터디] 김지나 #3주차 (0) | 2024.07.30 |
---|---|
[2024 여름방학 Node.js 스터디] 이종윤 #2주차 (1) | 2024.07.25 |
[2024 여름방학 Node.js 스터디] 김지나 #2주차 (3) | 2024.07.25 |
[2024 여름방학 Node.js 스터디] 이종윤 #1주차 (0) | 2024.07.18 |
[2024 여름방학 Node.js 스터디] 백채린 #1주차 1~2장 (0) | 2024.07.18 |