3.1 REPL 사용하기
3.1.1 REPL
R(Read), E(Evaluate), P(Print), L(Loop)
자바스크립트는 스크립트 언어라서 즉석에서 코드를 실행할 수 있다.
윈도우에서는 명령 프롬프트, 맥이나 리눅스에서는 터미널에 node 입력
프롬프트가 > 모양으로 바뀌면, 자바스크립트 코드 입력
입력한 값의 결과값이 바로 출력됨
· 간단한 코드를 테스트하는 용도
· 긴 코드를 입력하기에는 부적합
3.2 JS 파일 실행하기
3.2.1 JS 파일을 만들어 실행하기
node [자바스크립트 파일 경로]로 실행
3.3 모듈로 만들기
3.3.1 모듈
모듈 : 특정한 기능을 하는 함수나 변수들의 집합
· 노드는 자바스크립트 코드를 모듈로 만들 수 있다.
· 모듈로 만들면 여러 프로그램에서 재사용 가능
3.3.2 모듈 만들어보기
같은 폴더 내에 var.js, func.js, index.js 만들기
· 파일 끝에 module.exports로 모듈로 만들 값을 지정
· 다른 파일에서 requir(파일 경로)로 그 모듈의 내용을 가져올 수 있음
// var.js
const odd = '홀수';
const even = '짝수';
module.exports = {
odd,
even,
};
// func.js
const { odd, even } = require('./var');
function checkOddOrEven(num) {
if (num % 2) {
return odd;
}
return even;
}
module.exports = checkOddOrEven;
// index.js
const { odd, even } = require('./var');
const checkNumber = require('./func');
function checkStringOddOrEven(str) {
if (str.length % 2) {
return odd;
}
return even;
}
console.log(checkNumber(10)); // 짝수
console.log(checkStringOddOrEven('hello')); // 홀수
3.3.3 파일 간의 모듈 관계
3.3.4 module, exports
module.exports 외에도 exports로 모듈을 만들 수 있음
=> module.exports와 exports가 참조 관계이기 때문
· exports에 객체의 속성이 아닌 다른 값을 대입하면 참조 관계가 깨짐
// var.js
exports.odd = '홀수';
exports.even = '짝수';
3.3.5 this
· 노드에서 최상위 스코프의 this는 module.exports를 가리킴
· 그 외에는 브라우저의 자바스크립트와 동일
· 함수 선언문 내부의 this는 global(전역) 객체를 가리킴
console.log(this); // {}
console.log(this === module.exports); // true
console.log(this === exports); // true
function whatIsThis() {
console.log('function', this === exports, this === global);
}
whatIsThis(); // fucntion false true
3.3.6 require의 특성
· require가 제일 위에 올 필요 없음
· require.cache에 한 번 require 한 모듈에 대한 캐쉬 정보가 들어있음
· require.main은 노드 실행시 첫 모듈을 가리킴
3.3.7 순환 참조 주의
모듈 A가 B를 require 하고, B가 다시 A를 require 하는 경우
=> 무한 반복을 막기 위해 순환 참조 대상이 빈 객체가 됨
// dep1.js
const dep2 = require('./dep2');
console.log('require dep2', dep2);
module.exports = () => {
console.log('dep2', dep2);
};
// dep2.js
const dep1 = require('./dep1');
console.log('require dep1', dep1);
module.exports = () => {
console.log('dep1', dep1);
};
// dep-run.js
const dep1 = require('./dep1');
const dep2 = require('./dep2');
dep1();
dep2();
/*
require dep1 {}
require dep2 [Function (anonymous)]
dep2 [Function (anonymous)]
dep1 {}
(node:6616) Warning: Accessing non-existent property 'Symbol(nodejs.util.inspect.custom)' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
(node:6616) Warning: Accessing non-existent property 'constructor' of module exports inside circular dependency
(node:6616) Warning: Accessing non-existent property 'Symbol(Symbol.toStringTag)' of module exports inside circular dependency
(node:6616) Warning: Accessing non-existent property 'Symbol(nodejs.util.inspect.custom)' of module exports inside circular dependency
(node:6616) Warning: Accessing non-existent property 'constructor' of module exports inside circular dependency
(node:6616) Warning: Accessing non-existent property 'Symbol(Symbol.toStringTag)' of module exports inside circular dependency
*/
3.3.8 ES 모듈
자바스크립트 자체 모듈 시스템 문법이 생김
· mjs 확장자를 사용하거나 package.json에 type: "module"을 추가해야 함
// var.mjs
export const odd = '홀수';
export const even = '짝수';
// func.mjs
import { odd, even } from './var.mjs';
function checkOddOrEven(num) {
if (num % 2) {
return odd;
}
return even;
}
export default checkOddOrEven;
// index.mjs
import { odd, even } from './var';
import checkNumber from './func';
function checkStringOddOrEven(str) {
if (str.length % 2) {
return odd;
}
return even;
}
console.log(checkNumber(10)); // 짝수
console.log(checkStringOddOrEven('hello')); // 홀수
3.3.9 CommonJS와 ES 모듈의 차이
차이점 | 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 |
확장자 생략 | 가능 | 불가능 |
다이내믹 임포트 | 가능 | 불가능 |
인덱스(index) 생략 | 가능 (require('./folder')) |
불가능 (import './folder/index.mjs') |
top level await | 불가능 | 가능 |
__filename, __dirname, require, module.exports, exports |
사용 가능 | 사용 불가능 (__filename 대신 import.meta.url 사용) |
서로 간 호출 | 가능 |
3.3.10 다이내믹 임포트, top level await
코드 중간에 동적으로 불러올 수 있음
=> CommonJS에서는 require(), ES 모듈에서는 import()
mjs 파일에서 최상위 스코프에선 async 없이 await 할 수 있음
// dynamic.js
const a = false;
if (a) {
require('./func');
}
console.log('성공');
/*
성공
*/
// dynamic.mjs
const a = false;
if (a) {
import './func.mjs';
}
console.log('성공');
/*
file:///c:/Users/LeeEoJin/Desktop/node/dynamic.mjs:3
import './func.mjs';
^^^^^^^^^^^^
SyntaxError: Unexpected string
*/
// dynamic.mjs
const a = true;
if (a) {
const m1 = await import('./func.mjs');
console.log(m1);
const m2 = await import('./var.mjs');
console.log(m2);
}
/*
[Module: null prototype] { default: [Function: checkOddOrEven] }
[Module: null prototype] { even: '짝수', odd: '홀수' }
*/
3.3.11 __filename, __dirname
__filename : 현재 파일 경로
__dirname : 현재 폴더(디렉토리) 경로
ES 모듈에서는 쓸 수 없어서 import.meta.url을 써야 함
// filenmae.js
console.log(__filename); // c:\Users\LeeEoJin\Desktop\node\filename.js
console.log(__dirname); // c:\Users\LeeEoJin\Desktop\node
// filename.mjs
console.log(import.meta.url); // file:///c:/Users/LeeEoJin/Desktop/node/filename.mjs
3.4 노드 내장 객체 알아보기
3.4.1 global
노드의 전역 객체
· 브라우저의 window 같은 역할
· 모든 파일에서 접근 가능
· window처럼 생략 가능 (console, require도 global의 속성)
3.4.2 global 속성 공유
global 속성에 값을 대입하면 다른 파일에서도 사용 가능
// globalA.js
module.exports = () => global.message;
// globalB.js
const A = require('./globalA');
global.message = '안녕하세요';
console.log(A()); // 안녕하세요
3.4.3 console 객체
브라우저의 console 객체와 매우 유사
console.time console.timeEnd |
시간로깅 |
console.error | 에러 로깅 |
console.log | 평범한 로그 |
console.dir | 객체 로깅 |
console.trace | 호출스택 로깅 |
console.table | 테이블 로깅 |
3.4.4 타이머 메소드
set 메소드에 clear 메소드가 대응됨
=> set 메소드의 리턴 값(아이디)을 clear 메소드에 넣어 취소
setTimeout(콜백 함수, 밀리초) | 주어진 밀리초(1000분의 1초) 이후에 콜백 |
setInterval(콜백 함수, 밀리초) | 주어진 밀리초마다 콜백 함수를 반복 실행 |
setImmediate(콜백 함수) | 콜백 함수를 즉시 실행 |
clearTimeout(아이디) | setTimeout 취소 |
clearInterval(아이디) | setInterval 취소 |
clearImmediate(아이디) | setImmediate 취소 |
3.4.5 process
현재 실행 중인 노드 프로세스에 대한 정보를 담고 있음
process.version | 설치된 노드의 버전 |
process.arch | 프로세서 아키텍처의 정보 |
process.platform | 운영체제 플랫폼 정보 |
process.pid | 현재 프로세스의 아이디 |
process.uptime() | 프로세스가 시작된 후 흐른 시간(초 단위) |
process.execPath | 노드의 경로 |
process.cwd() | 현재 프로세스가 실행되는 위치 |
process.cpuUsage() | 현재 cpu 사용량 |
3.4.6 process.env
시스템 환경 변수들이 들어있는 객체
· 비밀키(데이터베이스 비밀번호, 서드파티 앱 키 등)를 보관하는 용도로 쓰임
· 환경 변수는 process.env로 접근 가능
const secretId = process.env.SECRET_ID;
const secretCode = process.env.SECRET_CODE;
일부 환경 변수는 노드 실행 시 영향을 미침
· NODE_OPTIONS : 노드 실행 옵션
· max-old-space-size : 노드가 사용할 수 있는 메모리를 지정하는 옵션
· UV_THREADPOOL_SIZE : 스레드풀 개수
NODE_OPTIONS=--max-old-space-size=8192
UV_THREADPOOL_SIZE=8
3.4.7 process.nextTick(콜백)
이벤트 루프가 다른 콜백 함수들보다 nextTick의 콜백 함수를 우선적으로 처리함
· 너무 남용하면 다른 콜백 함수들 실행이 늦어짐
· 비슷한 경우로 promise가 있음 (nextTick처럼 우선순위가 높음)
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끼리는 순서가 지켜짐
immediate와 timeout은 순서가 환경에 따라 달라짐
*/
3.4.8 마이크로태스크
Promise, nextTick 등
· setImmediate, setTimeout보다 먼저 실행됨
3.4.9 process.exit(코드)
현재의 프로세스를 멈춤
· 코드가 없거나 0이면 정상 종료
· 이외의 코드는 비정상 종료
process.exit(); // 정상 종료
process.exit(0); // 정상 종료
process.exit(1); // 비정상 종료
3.5 노드 내장 모듈 사용하기
3.5.1 os
운영체제의 정보를 담고 있음
모듈은 require로 가져옴 (내장 모듈이라 경로 대신 이름만 적어줘도 됨)
const os = require('os');
3.5.2 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() | 전체 메모리 용량 |
3.5.3 path
폴더와 파일의 경로를 쉽게 조작하도록 도와주는 모듈
운영체제별로 경로 구분자가 다름 (Windows: '\', POSIX: '/')
const path = require('path');
3.5.4 path 모듈 메소드
path.sep | 경로의 구분자 (Windows: '\', POSIX: '/') |
path.delimiter | 환경 변수의 구분자 (Windows: ';', POSIX: ':') |
path.dirname(경로) | 파일이 위치한 폴더 경로 |
path.extname(경로) | 파일의 확장자 |
path.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과 비슷하지만 '/'를 절대경로로 처리함 (path.join은 '/'를 상대경로로 처리함) |
3.5.5 알아둬야할 path 관련 정보
join과 resolve의 차이 : resolve는 '/'를 절대경로로 처리, join은 상대경로로 처리
path.join('/a', '/b', 'c'); // 결과 : /a/b/c/
path.resolve('/a', '/b', 'c'); // 결과 : /b/c
\\와 \ 차이 : \는 윈도우 경로 구분자, \\는 자바스크립트 문자열 안에서 사용(\가 특수문자라 \\로 이스케이프 해준 것)
윈도우에서 POSIX path를 사용하고 싶다면 : path.posix 객체 사용
POSIX에서 윈도우 path를 사용하고 싶다면 : path.win32 객체 사용
3.5.6 url 모듈
인터넷 주소를 쉽게 조작하도록 도와주는 모듈
url 처리에 크게 두 가지 방식이 있음 (기존 노드 방식, WHATWG 방식)
=> 요즘은 WHATWG 방식만 사용함
WHATWG 방식
const url = require('url'); // 생략 가능
const { URL } = url; // 생략 가능
const myURL = new URL('주소');
url 모듈 안에 URL 생성자가 있다. URL은 노드 내장 객체이기도 해서 require 할 필요는 없다.
3.5.7 searchParams
WHATWG 방식에서 쿼리스트링(search) 부분 처리를 도와주는 객체
getAll(키) | 키에 해당하는 모든 값들을 가져옴 |
get(키) | 키에 해당하는 첫 번재 값만 가져옴 |
has(키) | 해당 키가 있는지 없는지를 검사 |
keys() | searchParams의 모든 키를 반복기(iterator, ES2015 문법) 객체로 가져옴 |
values() | searchParams의 모든 값을 반복기 객체로 가져옴 |
append(키, 값) | 해당 키를 추가. 같은 키의 값이 있다면 유지하고 하나 더 추가 |
set(키, 값) | 해당 키를 추가. 같은 키의 값들을 모두 지우고 새로 추가 |
delete(키) | 해당 키를 제거 |
toString() | 조작한 searchParams 객체를 다시 문자열로 만듬 이 문자열을 search에 대입하면 주소 객체에 반영됨 |
3.5.8 dns
DNS를 다룰 때 사용하는 모듈
· 도메인을 통해 IP나 DNS 레코드를 얻고자 할 때 사용
A | ipv4 주소 |
AAAA | ipv6 주소 |
NS | 네임서버 |
SOA | 도메인 정보 |
CNAME | 별칭, 주로 www가 붙은 주소는 별칭인 경우가 많음 |
MX | 메일 서버 |
3.5.9 단방향 암호화(crypto)
암호화(평문 -> 암호)는 가능하지만 복호화(암호 -> 평문)는 불가능
단방향 암호화의 대표 주자 : 해시 기법
=> 문자열을 고정된 길이의 다른 문자열로 바꾸는 방식
3.5.10 Hash 사용하기(sha512)
· createHash(알고리즘) : 사용할 해시 알고리즘을 넣어준다
=> md5, sha1, sha256, sha512 등이 가능하지만, md5와 sha1은 이미 취약점이 발견됨
=> 현재는 sha512 정도로 충분하지만, 나중에 sha512마저도 취약해지면 더 강화된 알고리즘으로 바꿔야 함
· update(문자열) : 변환할 문자열을 넣어준다
· digest(인코딩) : 인코딩할 알고리즘을 넣어준다
=> base64, hex, latin1이 주로 사용되는데, 그중 base64가 결과 문자열이 가장 짧아 애용됨. 결과물로 변환된 문자열을 반환한다
3.5.11 pbkdf2
컴퓨터의 발달로 기존 암호화 알고리즘이 위협받고 있음
=> sha512가 취약해지면 sha3으로 넘어가야 함
=> 현재는 pbkdf2나, bcrypt, scrypt 알고리즘으로 비밀번호를 암호화 (Node는 pbkdf2와 scrypt 지원)
const crypto = require('crypto');
crypto.randomBytes(64, (err, buf) => {
const salt = buf.toString('base64');
console.log('salt:', salt);
crypto.pbkdf2('비밀번호', salt, 100000, 64, 'sha512', (err, key) => {
console.log('password:', key.toString('base64'));
});
});
/*
salt: lJiMT2VFVfeLFSAvlhD93ZOuxx4y84ODhdJIi/orJbCHSVEQRKlu3dPsRWWEFt4NQLgaj7V5/rwD6YP+j0SMFw==
password: P/8F5L3ez+mS21RteYxjy2GbQhGYssP9751Nls0sTBcQoxwKnWslVdvXpHLn9VTyhElp1VopVmp2u8ZEMHEeuQ==
*/
3.5.12 양방향 암호화
대칭형 암호화(암호문 복호화 가능)
=> Key가 사용됨
=> 암호화 할 때와 복호화 할 때 같은 Key를 사용해야 함
· crypto.createCipheriv(알고리즘, 키, iv) : 암호화 알고리즘과 키, 초기화벡터를 넣어준다
=> 사용 가능한 알고리즘 목록은 crypto.getCiphers()를 하면 볼 수 있음
=> 키는 32바이트, 초기화벡터(iv)는 16바이트로 고정
· cipher.update(문자열, 인코딩, 출력 인코딩) : 암호화 할 대상과 대상의 인코딩, 출력 결과물의 인코딩을 넣어준다
=> 보통 문자열은 utf8 인코딩, 암호는 base64를 많이 사용함
· cipher.final(출력 인코딩) : 출력 결과물의 인코딩을 넣어주면 암호화가 완료된다
· crypto.createDecipheriv(알고리즘, 키, iv) : 복호화 할 때 사용한다. 암호화 할 대 사용했던 알고리즘과 키, iv를 그대로 넣어주어야 한다
· decipher.final(출력 인코딩) : 복호화 결과물의 인코딩을 넣어준다
3.5.13 util
각종 편의 기능을 모아둔 모듈 (deprecated와 promisfy가 자주 쓰임)
· util.deprecate(함수, 경고 메시지) : 함수가 deprecated 처리되었음을 알려준다
=> 첫 번째 인자로 넣은 함수를 사용했을 때 경고 메시지가 출력된다
=> 두 번째 인자로 경고 메시지 내용을 넣으면 된다. 함수가 조만간 사라지거나 변경될 때 알려주는 역할
· util.promisfy(콜백 패턴 함수) : 콜백 패턴을 프로미스 패턴으로 바꿔준다
=> 바꿀 함수를 인자로 제공하면 된다. 이렇게 바꾸면 async/await 패턴까지 사용할 수 있다
3.5.14 worker_threads
노드에서 멀티 스레드 방식으로 작업할 수 있음
· isMainThread : 현재 코드가 메인 스레드에서 실행되는지, 워커 스레드에서 실행되는지 구분
(true : 메인 스레드, false : 워커 스레드)
메인 스레드에서는 new Worker를 통해 현재 파일(__filename)을 워커 스레드에서 실행시킴
· worker.postMessage : 부모에서 워커로 데이터를 보냄
· parentPort.on('message') : 부모로부터 데이터를 받음
// worker_threads.js
const {
Worker, isMainThread, parentPort,
} = require('worker_threads');
if (isMainThread) { // 부모일 때
const worker = new Worker(__filename);
worker.on('message', message => console.log('from worker', message));
worker.on('exit', () => console.log('worker exit'));
worker.postMessage('ping');
} else { // 워커일 때
parentPort.on('message', (value) => {
console.log('from parent', value);
parentPort.postMessage('pong');
parentPort.close();
});
}
/* 출력
from parent ping
from worker pong
worker exit
*/
// 여러 워커스레드 사용하기
// worker_threads.js
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', message => console.log('from worker', message));
worker.on('exit', () => {
threads.delete(worker);
if (threads.size === 0) {
console.log('job done');
}
});
}
} else { // 워커일 때
const data = workerData;
parentPort.postMessage(data.start + 100);
}
/* 출력
from worker 101
from worker 102
job done
*/
3.5.15 child_process
노드에서 다른 프로그램을 실행하고 싶거나 명령어를 수행하고 싶을 때 사용
=> 현재 노드 프로세스 외에 새로운 프로세스를 띄워서 명령을 수행함
=> 명령 프롬프트의 명령어인 dir을 노드를 통해 실행 (리눅스라면 ls)
const exec = requir('child_process').exec;
var process = exec('dir');
process.stdout.on('data', function(data) {
console.log(data.toString());
}); // 실행 결과
process.stderr.on('data', function(data) {
console.error(data.toString());
}); // 실행 에러
파이썬 프로그램 실행하기 (파이썬이 설치되어 있어야 함)
# test.py
print('hello')
const spawn = require('child_process').spawn;
var process = spawn('python', ['test.p']);
process.stdout.on('data', function(data) {
console.log(data.toString());
}); // 실행 결과
process.stderr.on('data', function(data) {
console.error(data.toString());
}); // 실행 에러
3.5.16 기타 모듈들
async_hooks | 비동기 코드의 흐름을 추적할 수 있는 실험적인 모듈 |
dgram | UPD와 관련된 작업을 할 때 사용 |
net | HTTP보다 로우 레벨인 TCP나 IPC 통신을 할 때 사용 |
perf_hooks | 성능 측정을 할 때 console.time보다 더 정교하게 측정 |
querystring | URLSearchParams가 나오기 이전에 쿼리스트링을 다루기 위해 사용했던 모듈 |
string_decoder | 버퍼 데이터를 문자열로 바꾸는 데 사용 |
tls | TLS와 SSL에 관련된 작업을 할 때 사용 |
tty | 터미널과 관련된 작업을 할 때 사용 |
v8 | V8 엔진에 직접 접근할 때 사용 |
vm | 가상 머신에 직접 접근할 때 사용 |
wasi | 웹어셈블리를 실행할 때 사용하는 실험적인 모듈 |
3.6 파일 시스템 접근하기
3.6.1 fs
파일 시스템에 접근하는 모듈
=> 파일/폴더 생성, 삭제, 읽기, 쓰기 가능
=> 웹 브라우저에서는 제한적이었으나 노드는 권한을 가지고 있음
// readme.txt에 적혀있는 것을 console에 출력
const fs = require('fs');
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log(data.toString());
});
3.6.2 fs 프로미스
콜백 방식 대신 프로미스 방식으로 사용 가능
const fs = require('fs').promises;
fs.readFile('./readme.txt')
.then((data) => {
console.log(data.toString());
})
.catch((err) => {
console.error(err);
});
3.6.3 fs로 파일 만들기
// writeme.txt를 만들고 '글이 입력됩니다'를 적은 뒤 console에 출력
const fs = require('fs').promises;
fs.writeFile('./writeme.txt', '글이 입력됩니다')
.then(() => {
return fs.readFile('./writeme.txt');
})
.then((data) => {
console.log(data.toString());
})
.catch((err) => {
console.error(err);
});
3.6.4 동기 메소드와 비동기 메소드
노드는 대부분의 내장 모듈 메소드를 비동기 방식으로 처리
const fs = require('fs');
console.log('시작');
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());
});
console.log('끝');
/*
출력 순서가 예상이 안 됨 (비동기 방식)
*/
· 동기와 비동기 : 백그라운드 작업 완료 확인 여부
· 블로킹과 논 블로킹 : 함수가 바로 return 되는지 여부
노드는 대부분 동기-블로킹 방식과 비동기-논 블로킹 방식임
3.6.5 동기 메소드 사용하기
const fs = require('fs');
console.log('시작');
let data = fs.readFileSync('./readme.txt');
console.log('1번', data.toString());
let data = fs.readFileSync('./readme.txt');
console.log('2번', data.toString());
let data = fs.readFileSync('./readme.txt');
console.log('3번', data.toString());
console.log('끝');
/*
출력이 순서대로 됨
*/
3.6.6 비동기 메소드로 순서 유지하기
콜백 형식 유지
=> 코드가 우측으로 너무 들어가는 현상 발생 (콜백 헬)
const fs = require('fs');
console.log('시작');
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('1번', data.toString());
console.log('끝');
});
});
});
/*
출력이 순서대로 됨
*/
프로미스로 극복
const fs = require('fs').promises;
console.log('시작');
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());
console.log('끝');
})
.catch((err) => {
console.error(err);
});
/*
출력이 순서대로 됨
*/
3.6.7 버퍼와 스트림 이해하기
· 버퍼 : 일정한 크기로 모아두는 데이터
=> 일정한 크기가 되면 한 번에 처리
=> 버퍼링 : 버퍼에 데이터가 찰 때까지 모으는 작업
· 스트림 : 데이터의 흐름
=> 일정한 크기로 나눠서 여러 번에 걸쳐서 처리
=> 버퍼(또는 청크)의 크기를 작게 만들어서 주기적으로 데이터를 전달
=> 스트리밍 : 일정한 크기의 데이터를 지속적으로 전달하는 작업
3.6.8 버퍼의 메소드
노드에서는 Buffer 객체 사용
· from(문자열) : 문자열을 버퍼로 바꿀 수 있다. length 속성은 버퍼의 크기를 알려준다 (바이트 단위)
· toString(버퍼) : 버퍼를 다시 문자열로 바꿀 수 있다. 이 때 base64나 hex를 인자로 넣으면 해당 인코딩으로도 변환할 수 있다.
· concat(배열) : 배열 안에 든 버퍼들을 하나로 합친다
· alloc(바이트) : 빈 버퍼를 생성한다. 바이트를 인자로 지정해주면 해당 크기의 버퍼가 생성된다
const buffer = Buffer.from('저를 버퍼로 바꿔보세요');
console.log('from():', buffer);
console.log('length:', buffer.length);
console.log('toString():', buffer.toString());
const array = [Buffer.from('띄엄 '), Buffer.from('띄엄 '), Buffer.from('띄어쓰기')];
const buffer2 = Buffer.concat(array);
console.log('concat():', buffer2.toString());
const buffer3 = Buffer.alloc(5);
console.log('alloc()', buffer3);
/*
from(): <Buffer ec a0 80 eb a5 bc 20 eb b2 84 ed 8d bc eb a1 9c 20 eb b0 94 ea bf 94 eb b3 b4 ec 84 b8 ec 9a 94>
length: 32
toString(): 저를 버퍼로 바꿔보세요
concat(): 띄엄 띄엄 띄어쓰기
alloc() <Buffer 00 00 00 00 00>
*/
3.6.9 파일 읽는 스트림 사용하기
· fs.createReadStream
=> createReadStream에 인자로 파일 경로와 옵션 객체 전달
=> highWaterMark 옵션은 버퍼의 크기(바이트 단위, 기본값 64KB)
=> data(chunk 전달), end(전달 완료), error(에러 발생) 이벤트 리스너와 같이 사용
const fs = require('fs');
const readStream = fs.createReadStream('./readme.txt', { highWaterMark: 16 });
const data = [];
readStream.on('data', (chunk) => { // 한 chunk씩 data 배열에 넣는 과정
data.push(chunk);
console.log('data :', chunk, chunk.length);
});
readStream.on('end', () => { // data에 있는 모든 chunk를 합쳐서 출력
console.log('end :', Buffer.concat(data).toString());
});
readStream.on('error', (err) => {
console.log('error :', err);
});
3.6.10 파일 쓰는 스트림 사용하기
· fs.createWriteStream
=> createReadStream에 인자로 파일 경로 전달
=> write로 chunk 입력, end로 스트림 종료
=> 스트림 종료 시 finish 이벤트 발생
const fs = require('fs');
const writeStream = fs.createWriteStream('./writeme.txt');
writeStream.on('finish', () => {
console.log('파일 쓰기 완료');
});
writeStream.write('데이터 1\n');
writeStream.write('데이터 2');
writeStream.end();
3.6.11 스트림 사이에 pipe 사용하기
pipe로 여러 개의 스트림을 이을 수 있음
const fs = require('fs');
const readStream = fs.createReadStream('readme.txt');
const writeStream = fs.createWriteStream('writeme.txt');
readStream.pipe(writeStream);
3.6.12 여러 개의 스트림 연결하기
zlib 내장 모듈을 사용해서 압축할 수 있음 (createGzip으로 .gz 파일 생성)
const zlib = require('zlib');
const fs = require('fs');
const readStream = fs.createReadStream('readme.txt');
const zlibStream = zlib.createGzip();
const writeStream = fs.createWriteStream('writeme.txt.gz');
readStream.pipe(zlibStream).pipe(writeStream);
3.6.13 기타 fs 메소드
· fs.access(경로, 옵션, 콜백) : 폴더나 파일에 접근할 수 있는지를 체크한다. 두 번째 인자로 상수들을 넣었다. F_OK는 파일 존재 여부, R_OK는 읽기 권한 여부, W_OK는 쓰기 권한 여부를 체크한다. 파일/폴더나 권한이 없다면 에러가 발생하는데, 파일/폴더가 없을 때의 에러 코드는 ENOENT다
· fs.mkdir(경로, 콜백) : 폴더를 만드는 메소드다. 이미 폴더가 있다면 에러를 발생하므로 먼저 access() 메소드를 호출해서 확인하는 것이 중요함
· fs.open(경로, 옵션, 콜백) : 파일의 아이디(fd 변수)를 가져오는 메소드다. 파일이 없다면 파일을 생성한 뒤 그 아이디를 가져온다. 가져온 아이디를 사용해 fs.read()나 fs.write()로 읽거나 쓸 수 있다. 두 번째 인자로 어떤 동작을 할 것인지 설정할 수 있다. 쓰려면 w, 읽으려면 r, 기존 파일에 추가하려면 a다.
· fs.rename(기존 경로, 새 경로, 콜백) : 파일의 이름을 바꾸는 메소드다. 기존 파일 위치와 새로운 파일 위치를 적어주면 된다. 반드시 같은 폴더를 지정할 필요는 없으므로 잘라내기 같은 기능을 할 수도 있다.
· fs.existsSync : 파일이나 폴더가 존재하는지
· fs.stat : 파일이 폴더인지 일반 파일인지
const fs = require('fs').promises;
const constants = require('fs').constants;
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((fd) => {
console.log('빈 파일 만들기 성공', fd);
fs.rename('./folder/file.js', './folder/newfile/js');
})
.then(() = > {
console.log('이름 바꾸기 성공');
})
.catch((err) => {
console.error(err);
});
· fs.readdir(경로, 콜백) : 폴더 안의 내용물을 확인할 수 있다. 배열 안에 내부 파일과 폴더명이 나옴
· fs.unlink(경로, 콜백) : 파일을 지울 수 있다. 파일이 없다면 에러가 발생하므로 먼저 파일이 있는지를 꼭 확인해야 함
· fs.rmdir(경로, 콜백) : 폴더를 지울 수 있다. 폴더 안에 파일이 있다면 에러가 발생하므로 먼저 내부 파일을 모두 지우고 호출해야 함
const fs = require('fs').promises;
fs.readdir('./folder')
.then((dir) => {
console.log('폴더 내용 확인', dir);
return fs.unlink('./folder/newFile.js');
})
.then(() => {
console.log('파일 삭제 성공');
return fs.rmdir('./folder');
})
.then(() => {
console.log('폴더 삭제 성공');
})
.catch((err) => {
console.error(err);
});
· fs.copyFile : 파일을 복사하는 방법
const fs = require('fs').promises;
fs.copyFile('readme.txt', 'writeme.txt')
.then(() => {
console.log('복사 완료');
})
.catch((error) => {
console.error(error);
});
· fs.watch : 파일을 감시하는 방법 (변경 사항 발생 시 이벤트 호출)
=> 내용물 수정시 eventType은 change, 파일명 변경 또는 파일 삭제시 eventType은 rename
const fs = require('fs');
fs.watch('./target.txt', (eventType, filename) => {
console.log(eventType, filename);
});
3.6.14 스레드풀 알아보기
fs, crypto, zlib 모듈의 메소드를 실행할 때는 백그라운드에서 동시에 실행됨
=> 스레드풀이 동시에 처리해줌
3.6.15 UV_THREAD_SIZE
스레드풀을 직접 컨트롤할 수는 없지만 개수 조절은 가능
=> 윈도우라면 터미널에 SET UV_THREADPOOL_SIZE=개수
=> 맥, 리눅스라면 UV_THREADPOOL_SIZE=개수
3.7 이벤트 이해하기
events 모듈로 커스텀 이벤트를 만들 수 있음
=> 스트림에 쓰였던 on('data'), on('end') 등과 비교
· on(이벤트명, 콜백) : 이벤트 이름과 이벤트 발생 시의 콜백을 연결해준다. 이렇게 연결하는 동작을 이벤트 리스닝이라고 부른다. 이벤트 하나에 이벤트 여러 개를 달아줄 수도 있다.
· addListener(이벤트명, 콜백) : on과 기능이 같다.
· emit(이벤트명) : 이벤트를 호출하는 메소드다. 이벤트 이름을 인자로 넣어주면 미리 등록해뒀던 이벤트 콜백이 실행된다.
· once(이벤트명, 콜백) : 한 번만 실행되는 이벤트다.
· removeAllListeners(이벤트명) : 이벤트에 연결된 모든 이벤트 리스너를 제거한다.
· removeListener(이벤트명, 리스너) : 이벤트에 연결된 리스너를 하나씩 제거한다.
· off(이벤트명, 콜백) : 노드 10 버전에서 추가된 메소드로, removeListener와 기능이 같다.
· listenerCount(이벤트명) : 현재 리스너가 몇 개 연결되어 있는지 확인한다.
const EventeEmitter = 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'); // 이벤트 호출
myEvent.emit('event2'); // 이벤트 호출 (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'); // 실행 안 됨
console.log(myEvent.listenerCount('event2')); // 2
3.8 예외 처리하기
3.8.1 예외 처리
예외(Exception) : 처리하지 못 한 에러
=> 노드 프로세스/스레드를 멈춤
=> 노드는 기본적으로 싱글 스레드라 스레드가 멈춘다는 것은 프로세스가 멈추는 것
=> 에러 처리는 필수
3.8.2 try catch문
기본적으로 try catch문으로 예외를 처리
=> 에러가 발생할 만한 곳을 try catch로 감쌈
setInterval(() => {
console.log('시작');
try {
throw new Error('에러');
} catch (err) {
console.error(err);
}
}, 1000);
3.8.3 노드 비동기 메소드의 에러
노드 비동기 메소드의 에러는 따로 처리하지 않아도 됨
=> 콜백 함수에서 에러 객체 제공
const fs = require('fs');
setInterval(() => {
fs.unlink('./abc.js', (err) => {
if (err) {
console.error(err);
)
});
}, 1000);
3.8.4 프로미스의 에러
프로미스의 에러는 따로 처리하지 않아도 됨
=> catch문이 없으면 warning이 뜸
=> 버전이 올라가면 동작이 바뀔 수 있음
const fs = require('fs').promises;
setInterval(() => {
fs.unlink('./abc.js')
}, 1000);
3.8.5 예측 불가능한 에러 처리하기
최후의 수단으로 사용
=> 콜백 함수의 동작이 보장되지 않음
=> 따라서 복구 작업용으로 쓰는 것은 부적합
=> 에러 내용 기록용으로만 쓰는게 좋음
process.on('uncaughtException', (err) => {
console.error('예기치 못한 에러', err);
});
setInterval(() => {
throw new Error('에러');
}, 1000);
setTimeout(() => {
console.log('실행');
}, 2000);
3.8.6 프로세스 종료하기
윈도우 | 맥/리눅스 |
![]() |
![]() |