본문 바로가기

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

[2024 여름방학 Node.js 스터디] 김지나 #2주차

반응형

3장. 노드 기능 알아보기

3.1. REPL 사용하기

- REPL: 입력한 코드를 읽고, 해석하고, 결과물을 반환하고, 종료할 때까지 반복해주는 환경

- REPL 사용하기

- REPL이 입력한 코드를 읽고 해석한 뒤 출력하고 입력을 기다림

- 짧은 코드 테스트 용도로 용이함. 긴 코드는 JS 파일로 만든 후 파일을 통째로 실행합시다!

 

3.2. JS 파일 실행하기

- JS 파일 만들기
- 실행이 되었쓰ㅃ니다

 

3.3. 모듈로 만들기

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

ex) 수학 코드 모듈 ...

- 모듈은 모듈 자체로 프로그램인 동시에 다른 프로그램의 부품으로도 사용 가능

- 모듈을 만들어두면 여러 프로그램에 해당 모듈을 재사용 가능

- 파일 하나가 모듈 하나, 파일별로 코드 모듈화 가능

 

3.3.1. CommonJS 모듈

- 변수 두 개를 선언하고 module.exports에 변수를 담은 객체를 대입 // 변수들을 모아둔 모듈
- require 함수 안에 불러올 모듈의 경로 적기, checkOddOrEven 함수를 모듈로 내보내서 다른 파일에서 모듈을 사용할 때 함수도 같이 사용할 수 있게 해 줌
- 모듈 하나가 여러 개의 모듈 사용
- index.js 실행

- 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 지정한 폴더나 파일이 존재하지 않는 경우

 

반응형