3장. 노드 기능 알아보기
3.1. REPL 사용하기
- REPL: 입력한 코드를 읽고, 해석하고, 결과물을 반환하고, 종료할 때까지 반복해주는 환경
- REPL 사용하기
- 짧은 코드 테스트 용도로 용이함. 긴 코드는 JS 파일로 만든 후 파일을 통째로 실행합시다!
3.2. JS 파일 실행하기
3.3. 모듈로 만들기
- 모듈: 특정 기능을 하는 함수나 변수들의 집합
ex) 수학 코드 모듈 ...
- 모듈은 모듈 자체로 프로그램인 동시에 다른 프로그램의 부품으로도 사용 가능
- 모듈을 만들어두면 여러 프로그램에 해당 모듈을 재사용 가능
- 파일 하나가 모듈 하나, 파일별로 코드 모듈화 가능
3.3.1. CommonJS 모듈
- module 객체 말고 export 객체 사용 가능
exports.odd = 'CJS 홀수입니다';
exports.even = 'CJS 짝수입니다';
- 각각의 변수를 exports 객체에 하나씩 넣었는데도 동일하게 동작하는 이유 = module.exports, exports가 같은 객체를 참조하기 때문
=> exports 객체에 add 함수를 넣으면 module.exports에도 add 함수가 들어감
※ exports 객체 사용 시 주의 사항
- module.exports와 참조 관계가 깨지지 않도록 주의!! exports에는 반드시 속성명과 속성값을 대입해야 합니다
- exports는 객체만 사용할 수 있으므로 module.exports에 함수를 대입한 경우에는 바꿀 수 없습니다
- 한 모듈에 exports 객체와 module.exports를 동시에 사용하지 않도록 합시다
- require 함수
- require가 반드시 파일 최상단에 위치할 필요는 xxx
- 한번 require한 파일은 require.cache에 저장됨 -> 다음번에 require할 때는 require.cache에 있는 것이 재사용됨
- 만약 새로 require 하고 싶으면 require.cache 속성을 제거하고 다시 require하면 됨.. 하지만 프로그램 동작이 꼬일 수 있으므로 되도록 하지 말자
- require.main은 노드 실행 시 첫 모듈을 가리킴
- node require로 노드를 실행했다면 require.js가 require.main이 됨
- 현재 파일이 첫 모듈인지 확인하고 싶다면 require.main === module을 해보면 됨
- 첫 모듈의 이름을 확인하고 싶다면 require.main.filename으로 확인하면 됨
- 순환 참조
: 참조하는 대상이 서로 물려 있어서 참조할 수 없게 되는 현상
//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하고 있는 모듈 두 개가 있움. dep-run.js를 실행해보면
- dep1의 module.exports가 빈 객체로 표시됨 -> 순환 참조
- 순환 참조가 있으면 순환 참조되는 대상을 빈 객체로 만듦
3.3.2. ECMAScript 모듈
- 공식적인 자바스크립트 모듈 형식
- 이전 절의 코드를 ES 모듈 형식으로 바꿔봅시다!
// var.mjs
export const odd = 'MJS 홀수입니다';
export const even = 'MJS 짝수입니다';
// 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.mjs';
import checkNumber from './func.mjs';
function checkStringOddOrEven(str) {
if (str.length % 2) { // 홀수이면
return odd;
}
return even;
}
console.log(checkNumber(10));
console.log(checkStringOddOrEven('hello'));
- require -> import / exports -> export / module.exports -> export default
- ES 모듈에서 import, export default는 함수나 객체가 아니라 문법!! 그 자체임
- 확장자 .mjs (.js에서 ES 모듈을 사용하려면 package.json에 type: "module" 속성 넣기)
- import 시 파일 경로에서 확장자 생략 불가능
차이점 | 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. 다이내믹 임포트
- 다이내믹 임포트: 조건부로 모듈을 불러오는 것
// dynamic.js
const a = false;
if (a) {
require('./func');
}
console.log('성공');
- 이 노드를 실행해보면 성공이 출력됩니다. require('./func')은 실행되지 않았습니다. a가 false기 때문에!
- 이 코드를 ES 모듈 형식으로 바꿔보면
// dynamic.mjs
const a = false;
if (a) {
import './func.mjs';
}
console.log('성공');
- 이걸 실행하면 오류가 뜹니다. 왜냐묜 ES 모듈에서는 if문 안에 import하는 것이 불가능하기 때문!!!
-> 이럴 때 다이내믹 임포트를 사용합시다
//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);
}
// 콘솔
$ node dynamic.mjs
[Module: null prototype] { default: [Function: checkOddOrEven] }
[Module: null prototype] { even: 'MJS 짝수입니다', odd: 'MJS 홀수입니다' }
- import는 프로미스를 반환하기 때문에 await이나 then을 붙여야 함!!
- ES 모듈의 최상위 스코프에서는 async 함수 없이도 await할 수 있습니다(대박)
3.3.4. __filename, __dirname
- __filename, __dirname 키워드로 경로에 대한 정보를 제공함
- 파일에 __filename, __dirname을 넣으면 실행 시 현재 파일명과 현재 파일 경로로 바뀜
- ES 모듈에서는 __filename, __dirname을 사용하지 못 하므로 import.meta.url을 사용해서 경로를 가져오도록 하자
3.4. 노드 내장 객체 알아보기
1) global 객체
- 전역 객체
- 파일 간의 데이터 공유 시 사용
//globalA.js
module.exports = () => global.message;
//globalB.js
const A = require('./globalA');
global.message = '안녕하세요';
console.log(A());
$ node globalB
안녕하세요
- globalB에서 넣은 global.message 값을 globalA에서도 접근 가능
2) console
- 디버깅 목적
console.time(레이블) | 같은 레이블을 가진 time과 timeEnd 사이의 시간 측정 |
console.log(내용) | 평범한 로그를 콘솔에 표시 |
console.error(에러 내용) | 에러를 콘솔에 표시 |
console.table(배열) | 배열의 요소로 객체 리터럴을 넣으면, 객체의 속성들이 테이블 형식으로 표현됨 |
console.dir(객체, 옵션) | 객체를 콘솔에 표시할 때 사용 |
console.trace(레이블) | 에러가 어디서 발생했는지 추적할 수 있게 함 |
3) 타이머
- 타이머 기능 제공
- setTimeout(콜백 함수 실행), setInterval(콜백 함수 반복 실행), setImmediate(콜백 함수 즉시 실행)
- clearTimeout, clearInterval, clearImmediate는 각각을 취소
- 콜백 기반 API지만 프로미스 방식으로도 사용 가능(노드 내장 모듈)
4) process
- 현재 실행되고 있는 노드 프로세스에 대한 정보
process.version | 설치된 노드 버전 |
process. arch | 프로세서 아키텍처 정보 |
process.platform | 운영체제 플랫폼 정보 |
process.pid | 현재 프로세스 아이디 |
process.uptime() | 프로세스가 시작된 후 흐른 시간 |
process.execPath | 노드 경로 |
process.cwd() | 현재 프로세스가 실행되는 위치 |
process.cpuUsage() | 현재 cpu 사용량 |
process.env | 시스템의 환경 변수 |
process.nextTick(콜백) | 이벤트 루프가 nextTick 콜백 함수를 가장 먼저 처리 |
process.exit(코드) | 실행 중인 프로세스 종료 |
3.5. 노드 내장 모듈 사용하기
1) os
- 운영체제의 정보를 가져옴
- require('os') 또는 require('node:os')
2) path
- 폴더와 파일의 경로를 쉽게 조작하도록 도와줌
- 운영체제 별로 경로 구분자(\ , /)가 다르기 때문에 필요함
3) url
- 인터넷 주소를 쉽게 조작하도록 도와주는 모듈
- url 모듈 안에 URL 생성자가 있는데, 이는 노드 내장 객체기 때문에 따로 require할 필요 xx
4) dns
- DNS를 다룰 때 사용하는 모듈
- 도메인을 통해 IP나 DNS 정보를 얻고자 할 때 사용
5) crypto
- 다양한 방식의 암호화를 도와주는 모듈
- 단방향 암호화: 복호화(암호화된 문자열을 원래 문자열로 되돌려놓는 것)할 수 없는 암호화 방식 = 해시 함수
=> 한번 암호화하면 원래 문자열을 찾을 수 없음
- 양방향 암호화: 복호화 가능, 키 사용
6) util
- 각종 편의 기능을 모아둔 모듈
7) worker_threads
- 멀티 스레드 방식으로 작업 가능(여러 개의 워커 스레드)
8) child_process
- 노드에서 다른 프로그램을 실행하고 싶거나 명령어를 수행하고 싶을 때 (ex: 파이썬 ... )
3.6. 파일 시스템 접근하기
- fs 모듈: 파일 시스템에 접근하는 모듈
3.6.1. 동기 메서드와 비동기 메서드
- 비동기 메서드: 백그라운드에 해당 파일을 읽으라고만 요청하고 다음 작업으로 넘어감
//async.js
onst fs = require('fs');
console.log('시작');
fs.readFile('./readme2.txt', (err, data) => {
if (err) {
throw err;
}
console.log('1번', data.toString());
});
fs.readFile('./readme2.txt', (err, data) => {
if (err) {
throw err;
}
console.log('2번', data.toString());
});
fs.readFile('./readme2.txt', (err, data) => {
if (err) {
throw err;
}
console.log('3번', data.toString());
});
console.log('끝');
// 콘솔
$ node async
시작
끝
2번 저를 여러 번 읽어보세요.
3번 저를 여러 번 읽어보세요.
1번 저를 여러 번 읽어보세요.
- 동기 메서드
//sync.js
const fs = require('fs');
console.log('시작');
let data = fs.readFileSync('./readme2.txt');
console.log('1번', data.toString());
data = fs.readFileSync('./readme2.txt');
console.log('2번', data.toString());
data = fs.readFileSync('./readme2.txt');
console.log('3번', data.toString());
console.log('끝');
// 콘솔
$ node sync
시작
1번 저를 여러 번 읽어보세요.
2번 저를 여러 번 읽어보세요.
3번 저를 여러 번 읽어보세요.
끝
- 단점: 백그라운드가 작업하는 동안 스레드는 아무것도 못 함 => 비효율적!!!!!!
- 비동기 메서드 >> 동기 메서드 (효율성)
3.6.2. 버퍼와 스트림 이해하기
- 파일을 읽거나 쓰는 방식
1) 버퍼
- 메모리에 저장된 데이터
- Buffer: 버퍼를 직접 다룰 수 있는 클래스
2) 스트림
- 버퍼의 크기를 작게 만들고 여러 번에 걸쳐 나눠 보내는 방식
- createReadStream: 파일 읽는 스트림 메서드 ( 이벤트 리스너 붙여서 사용 )
- 파이핑: 스트림끼리 연결하는 것
3.6.3. 스레드 풀 알아보기
- 동시에 실행되는 작업들 관리
3.7. 이벤트 이해하기
- 이벤트는 직접 만들 수 있어서 다양한 동작 구현 가능
- 웹 서버 구축 시 주로 사용
on(이벤트명, 콜백) | 이벤트 이름과 이벤트 발생 시의 콜백을 연결 |
addListener(이벤트명, 콜백) | on과 기능이 같음 |
emit(이벤트명) | 이벤트를 호출하는 메서드 |
once(이벤트명, 콜백) | 한 번만 실행되는 이벤트 |
removeAllListeners(이벤트명) | 이벤트에 연결된 모든 이벤트 리스너를 제거 |
removeListener(이벤트명, 리스너) | 이벤트에 연결된 리스너를 하나씩 제거 |
off(이벤트명, 콜백) | removeListener와 기능이 같음 |
listenerCount(이벤트명) | 현재 리스너가 몇 개 연결되어 있는지 확인 |
3.8. 예외 처리하기
- 예외: 처리하지 못 한 에러
- 노드에서 메인 스레드가 에러로 멈추면 정말정말정말정말 큰 사고이기 때문에 에러를 처리하는 방법을 꼭!! 익혀두자
- try/catch, throw, uncaughtException 이벤트 리스너-process.exit() 이용
3.8.1. 자주 발생하는 에러들
node: command not found | 노드를 설치했지만 이 에러가 발생하는 경우는 환경 변수가 제대로 설정 안 됨 |
ReferenceError: 모듈 is not defined | 모듈을 require했는지 확인 |
Error: Cannot find module 모듈명 | 해당 모듈을 require했지만 설치하지 않음 |
Error [ERR_MODULE_NOT_FOUND] | 존재하지 않는 모듈을 불러옴 |
Error: Can't set headers after they are sent | 요청에 대한 응답을 보낼 때 응답을 두 번 이상 보냄 |
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed- JavaScript heap out of memory | 코드를 실행할 때 메모리가 부족해서 스크립트가 정상적으로 작동하지 않는 경우 |
UnhandledPromiseRejectionWarning: Unhandled promise rejection | 프로미스 사용 시 catch 메서드를 붙이지 않으면 발생 |
EADDRINUSE 포트 번호 | 해당 포트 번호에 이미 다른 프로세스가 연결되어 있음 |
EACCES 또는 EPERM | 노드가 작업을 수행하는 데 권한이 충분하지 않음 |
EJSONPARSE | JSON 파일에 문법 오류 |
ECONNREFUSED | 요청을 보냈으나 연결이 성립하지 않음 |
ETARGET | 패키지 버전이 존재하지 않음 |
ETIMEOUT | 요청을 보냈으나 응답이 시간 내에 오지 않을 때 발생 |
ENOENT: no such file or directory | 지정한 폴더나 파일이 존재하지 않는 경우 |
'WINK-(Web & App) > Express.js (Node.js) 스터디' 카테고리의 다른 글
[2024 여름방학 Node.js 스터디] 이종윤 #2주차 (1) | 2024.07.25 |
---|---|
[2024 여름방학 Node.js 스터디] 백채린 #2주차 3장 (0) | 2024.07.25 |
[2024 여름방학 Node.js 스터디] 이종윤 #1주차 (0) | 2024.07.18 |
[2024 여름방학 Node.js 스터디] 백채린 #1주차 1~2장 (0) | 2024.07.18 |
[2024 여름방학 Node.js 스터디] 김지나 #1주차 (0) | 2024.07.18 |