웹 서비스 개발(FB,BE,SERVER,DB)/Node.js

3. Node.js_Basic2

Zoo_10th 2024. 3. 11.

1. 파일 시스템

1-1. fs 모듈

Node.js의 fs (File System) 모듈은 파일 시스템에 대한 다양한 작업을 가능하게 하는 내장 모듈이다. 이 모듈을 사용하면 파일을 생성하거나, 읽고, 쓰고, 삭제하는 등의 작업을 수행할 수 있다.

 - fs 모듈의 기본 사용법

fs 모듈은 기본적으로 콜백 기반으로 작동하지만, fs.promises API를 통해 프로미스 기반으로도 사용할 수 있다. 프로미스 방식은 async/await과 함께 사용할 때 코드가 더 간결하고 이해하기 쉬워진다.

 - 파일 읽기 예시

const fs = require('fs').promises;

async function readFile() {
  try {
    const data = await fs.readFile('example.txt', 'utf8');
    console.log(data);
  } catch (error) {
    console.error('파일 읽기 중 에러 발생:', error);
  }
}

readFile();

 - 파일 쓰기 예시

const fs = require('fs').promises;

async function writeFile() {
  try {
    await fs.writeFile('example.txt', 'Hello, Node.js!', 'utf8');
    console.log('파일 쓰기 완료');
  } catch (error) {
    console.error('파일 쓰기 중 에러 발생:', error);
  }
}

writeFile();

1-2. async 와 await

async와 await은 JavaScript에서 비동기 코드를 작성하는 간결하고 직관적인 방법을 제공한다.

 - async

1) async는 함수 앞에 사용된다. async 키워드가 붙은 함수는 항상 프로미스를 반환한다.

2) 함수 내부에서 일반 값을 반환하더라도, 그 값은 자동으로 프로미스로 감싸져 반환된다.

3) 만약 함수가 프로미스를 반환한다면, 그 프로미스는 그대로 반환된다.

 - await

1) await 연산자는 async 함수 내에서만 사용할 수 있다.

2) await는 프로미스가 처리될 때까지 함수의 실행을 일시 중지하고, 프로미스의 결과값을 반환한다.

3) 프로미스가 이행(fulfilled)되면, 결과값으로 이행 값이 반환된다.

4) 프로미스가 거부(rejected)되면, 예외가 발생하여 try...catch 블록을 통해 처리할 수 있다.

async function fetchData() {
  try {
    const response = await fetch('<https://example.com/data>');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('데이터 가져오기 실패:', error);
  }
}

 - 장점

1) async와 await를 사용하면, 프로미스의 .then()과 .catch() 체인 대신, 더 읽기 쉽고, 동기 코드와 유사한 방식으로 비동기 코드를 작성할 수 있다.

2) 복잡한 비동기 로직을 간결하고 직관적으로 표현할 수 있어 코드의 가독성이 향상된다.

async와 await는 JavaScript의 비동기 프로그래밍을 단순화하고 가독성을 높이는 도구이다

1-3. 동기와 비동기 Method

Node.js에서 동기적(Synchronous)과 비동기적(Asynchronous) 방식은 코드 실행의 순서와 관련이 있다. 동기적 방식은 코드가 작성된 순서대로 실행되며, 비동기적 방식은 작업의 완료 시점이 코드의 작성 순서와 일치하지 않을 수 있다.

 - 비동기 방식

1) Node.js의 대부분의 I/O 작업은 비동기 방식으로 처리된다. 예를 들어, 파일을 읽거나 쓰는 작업, 네트워크 요청 등이 이에 해당한다.

2) 비동기 방식은 백그라운드에서 작업을 처리하고, 작업이 완료되면 콜백 함수를 실행하여 결과를 처리한다. 이 때문에 작업의 완료 시점이 코드의 작성 순서와 다를 수 있다.

3) 비동기 방식은 논 블로킹(Non-Blocking) 특성을 가진다. 즉, 함수가 바로 반환되고, 결과는 나중에 콜백을 통해 전달된다.

 - 동기 방식

1) 일부 Node.js의 메서드는 동기적으로도 사용할 수 있다. 이러한 메서드는 주로 'Sync' 접미사를 가지고 있다 (예: fs.readFileSync).

2) 동기 방식은 작업이 완료될 때까지 다음 코드의 실행을 차단한다. 이는 블로킹(Blocking) 특성이라고 한다.
3) 동기 방식은 코드의 흐름을 쉽게 이해하고 예측할 수 있도록 해준다. 하지만, 긴 작업의 경우 애플리케이션의 전체적인 응답성이 저하될 수 있다.

 - 비동기 작업의 순서 제어

1) 콜백 함수: 첫 번째 작업의 콜백 내부에서 두 번째 작업을 시작하는 방식으로 순서를 제어한다.

2) 프로미스(Promises): 프로미스를 사용하여 비동기 작업을 체인(chain)으로 연결한다.

3) async/await: async/await를 사용하여 비동기 코드를 동기적인 방식으로 작성한다.

const fs = require('fs');

// 비동기 방식으로 파일 읽기
fs.readFile('file1.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// 동기 방식으로 파일 읽기
const data = fs.readFileSync('file2.txt', 'utf8');
console.log(data);

1-4. 버퍼와 스트림

Node.js의 fs 모듈을 통해 파일 시스템에 접근할 수 있으며, 이를 통해 파일과 폴더를 생성, 삭제, 읽기, 쓰기 등의 작업을 할 수 있다.

 - 버퍼(Buffer)

1) 버퍼는 일정한 크기로 데이터를 모아두는 임시 저장소이다. 데이터가 버퍼의 크기만큼 차면 한 번에 처리된다.

2) Node.js에서는 Buffer 객체를 사용하여 바이너리 데이터를 다룰 수 있다. 예를 들어, Buffer.from(문자열)은 문자열을 버퍼로 변환한다.

 - 스트림(Stream)

1) 스트림은 데이터를 조각(청크)으로 나누어 처리하는 방식이다. 스트림을 사용하면 데이터를 일정한 크기의 청크로 나누어 주기적으로 전송하고 처리할 수 있다.

2) Node.js에서는 fs.createReadStream과 fs.createWriteStream을 사용하여 파일을 읽고 쓰는 스트림을 생성할 수 있다.

 - 파일 읽기 스트림

const fs = require('fs');

const readStream = fs.createReadStream('example.txt', {
  highWaterMark: 16 // 버퍼(청크)의 크기를 16바이트로 설정
});

readStream.on('data', (chunk) => {
  console.log('읽은 데이터:', chunk);
});

readStream.on('end', () => {
  console.log('읽기 완료');
});

readStream.on('error', (err) => {
  console.error('읽기 에러 발생:', err);
});

 - 파일 쓰기 스트림

const fs = require('fs');

const writeStream = fs.createWriteStream('example.txt');
writeStream.write('이것은 첫 번째 청크입니다.');
writeStream.write('이것은 두 번째 청크입니다.');
writeStream.end('마지막 청크입니다.');

writeStream.on('finish', () => {
  console.log('쓰기 완료');
});

 - 스트림 파이핑

스트림 간에 pipe() 메서드를 사용하여 데이터를 쉽게 전달할 수 있다. 예를 들어, 파일을 읽는 스트림과 쓰는 스트림을 연결할 수 있다.

const fs = require('fs');

const readStream = fs.createReadStream('example.txt');
const writeStream = fs.createWriteStream('example2.txt');
readStream.pipe(writeStream);

 - 메모리 사용 비교

버퍼 방식은 전체 데이터를 메모리에 로드하지만, 스트림 방식은 청크 단위로 데이터를 처리하기 때문에 메모리 사용량이 상대적으로 적다.

const fs = require('fs');
const file = fs.createWriteStream('./big.txt');

for (let i = 0; i <= 10000000; i++) {
  file.write('안녕하세요. 엄청나게 큰 파일을 만들어 볼 것이다. 각오 단단히 하세요!\n');
}
file.end();
const fs = require('fs');

console.log('before: ', process.memoryUsage().rss);

const data1 = fs.readFileSync('./big.txt');
fs.writeFileSync('./big2.txt', data1);
console.log('buffer: ', process.memoryUsage().rss);
const fs = require('fs');

console.log('before: ', process.memoryUsage().rss);

const readStream = fs.createReadStream('./big.txt');
const writeStream = fs.createWriteStream('./big3.txt');
readStream.pipe(writeStream);
readStream.on('end', () => {
  console.log('stream: ', process.memoryUsage().rss);
});

 - 파일 변경 감시

fs.watch 함수를 사용하면 파일이나 폴더의 변경을 감시하고, 변경이 발생하면 이벤트를 발생시킬 수 있다.

const fs = require('fs');

fs.watch('./target.txt', (eventType, filename) => {
  console.log(eventType, filename);
});

 - 스레드풀

Node.js는 기본적으로 작업을 위한 스레드풀을 사용한다. 스레드풀의 크기는 환경 변수 UV_THREADPOOL_SIZE를 통해 조정할 수 있다.

const crypto = require('crypto');

const pass = 'pass';
const salt = 'salt';
const start = Date.now();

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('1:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('2:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('3:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('4:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('5:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('6:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('7:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('8:', Date.now() - start);
});

2. Event

2-1. custom event

Node.js의 events 모듈은 이벤트 기반의 프로그래밍을 가능하게 하는 중요한 부분이다. 이 모듈을 사용하여 사용자 정의 이벤트를 만들고, 이벤트 리스너를 등록하며, 이벤트를 발생시킬 수 있다. events 모듈을 사용하여 생성한 이벤트는 Node.js의 내장 스트림 객체가 사용하는 on('data'), on('end') 같은 이벤트와 유사한 방식으로 작동한다.

const EventEmitter = 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'); // 이벤트 호출

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'));

3. 예외 처리

3-1. 예외 처리

Node.js에서 예외 처리는 매우 중요하다. 예외(Exception)는 처리되지 않은 에러를 의미하며, 이러한 예외가 발생하면 Node.js 애플리케이션(프로세스)이 멈출 수 있다. Node.js는 기본적으로 싱글 스레드로 실행되기 때문에, 이러한 예외 상황은 전체 애플리케이션의 중단을 의미할 수 있다.

 - 예외 처리 방법

1) try...catch 문: 동기 코드에서 발생할 수 있는 예외를 캐치하기 위해 사용된다. 에러가 발생할 것으로 예상되는 코드 블록을 try 블록으로 감싸고, 에러를 catch 블록에서 처리한다.

try {
  // 에러가 발생할 가능성이 있는 코드
} catch (error) {
  // 에러 처리
}

2) 비동기 메서드의 콜백 함수: Node.js의 대부분의 비동기 메서드는 콜백 함수의 첫 번째 인자로 에러 객체를 제공한다. 이를 통해 에러를 적절히 처리할 수 있다.

fs.readFile('somefile.txt', (err, data) => {
  if (err) {
    // 에러 처리
  }
  // 정상적인 처리
});

3) 프로미스의 에러 처리: 프로미스를 사용하는 경우 .catch() 메서드나 try...catch 블록(비동기 함수 내부에서 await를 사용하는 경우)을 통해 에러를 처리할 수 있다.

someAsyncFunction()
  .then(data => {
    // 정상적인 처리
  })
  .catch(error => {
    // 에러 처리
  });

 - 프로세스 종료와 예외처리

1) 예외가 발생했을 때 프로세스를 종료하는 것은 최후의 수단으로 고려해야 한다.

2) 예외 처리를 통해 에러를 기록하고, 필요한 경우 애플리케이션을 안정적으로 종료하거나 재시작하는 로직을 구현해야한다.

3) Node.js에서는 process.on('uncaughtException', callback)을 사용하여 처리되지 않은 예외를 잡을 수 있다. 그러나 이 방법은 예외적인 상황에서만 사용해야 한다.

process.on('uncaughtException', (err) => {
  // 예외 처리 및 로깅
  console.error('예기치 못한 에러', err);
  process.exit(1); // 프로세스 종료
});
728x90

'웹 서비스 개발(FB,BE,SERVER,DB) > Node.js' 카테고리의 다른 글

5. Node.js WebServer 2  (0) 2024.03.13
4. Node.js WebServer  (0) 2024.03.12
2. Node.js_Basic  (0) 2024.03.11
0. Node.js 설치하기  (0) 2024.03.11
1. Node.js  (0) 2024.03.11

댓글