Back-end/Node.js

[Node.js] 파일 시스템 모듈(fs), 이벤트

poppy 2021. 7. 9. 15:13
반응형

파일 시스템 모듈인 fs 모듈과 이벤트에 대해 알아보겠습니다.

 

fs 모듈

fs 모듈은 파일 시스템에 접근하는 모듈입니다. 파일을 생성하거나 삭제하고 일고 쓸 수 있습니다. fs 모듈의 여러 가지 메소드에 대해 알아보겠습니다.

 

 파일 읽기/쓰기 

1. 파일 읽기 - readFile

const fs = require('fs');

fs.readFile('./readme.txt', (err, data) => {
  if (err) {
    throw err;
  }
  console.log(data);
  console.log(data.toString());
});

출력 결과

fs.readFile(파일 경로) - fs 모듈을 불러온 뒤 파일 경로를 지정하면 파일을 읽을 수 있습니다. readFile의 결과물은 버퍼 형식이므로 사람이 읽을 수 있는 형식으로 바꾸기 위해서는 toString()을 사용해야 합니다.

 

실무에서는 fs가 콜백 형식의 모듈이므로 사용하기 불편하므로 fs모듈을 프로미스 형식으로 바꿔주는 방법을 사용합니다. 다음 코드는 fs모듈을 프로미스 형식으로 바꾸어 사용한 코드입니다.

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

fs.readFile('./readme.txt')
  .then((data) => {
    console.log(data);
    console.log(data.toString());
  })
  .catch((err) => {
    console.error(err);
  });

 

2. 파일 쓰기 - writeFile

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

출력 결과

fs.writeFile(파일을 저장할 경로, 파일 내용) - 파일을 저장할 경로와 파일 내용을 입력하면 파일을 쓸 수 있습니다. 

 

 동기와 비동기 

1. 비동기

const 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('끝');

출력 결과

메서드가 비동기로 동작하기 때문에 출력의 순서와 코드의 순서가 다릅니다. 비동기 메서드들은 백그라운드에 해당 파일을 읽으라고 요청만 하고 다음 작업으로 넘어갑니다. 따라서 이 코드에서 파일 읽기 요청을 세 번한 후 바로 console.log('끝') 을 처리합니다. 나중에 요청된 읽기가 완료되면 백그라운드가 다시 메인 스레드에 알리고 메인 스레드가 그 때 등록된 콜백 함수를 실행합니다.

 

2. 동기

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

출력 결과

동기 메서들은 이름 뒤에 Sync가 붙어 있습니다. 동기 메서드인 readFileSync를 사용하면 코드의 순서와 출력의 순서가 같은 것을 확인할 수 있습니다. 하지만 동기 메서드를 사용하면 요청이 수백 개 이상 들어올 때 성능에 문제가 생깁니다. 동기 방식은 이전 작업이 완료될 때까지 다음 작업을 진행할 수 없으므로 백그라운드가 작업하는 동안 메인 스레드는 아무것도 하지 못하고 대기하게 됩니다. 따라서 매우 비효율적입니다.

 

비동기 방식으로 하되 순서를 유지하기 위해서는 readFile의 콜백에 다음 readFile을 넣으면 됩니다. 하지만 이 방법은 콜백 지옥이 생깁니다. 콜백 지옥이 생기지 않고 순서를 유지하고 싶을 때는 promise 나 async/await를 사용하면 됩니다.

 

 버퍼와 스트림 

1. 버퍼(Buffer)

파일을 읽을 때 메모리에 파일 크기만큼 공간을 비워두고 파일 데이터를 메모리를 저장합니다. 이 때 메모리에 저장된 데이터가 버퍼입니다. 

const buffer = Buffer.from('저를 버퍼로 바꿔보세요');
console.log('from():', buffer);
console.log('toString():', buffer.toString());

const array = [Buffer.from('띄엄 '), Buffer.from('띄엄 '), Buffer.from('띄어쓰기')];
const buffer2 = Buffer.concat(array);
console.log('concat():', buffer2.toString());

from(문자열) - 문자열을 버퍼로 바꾼다

toString(버퍼) - 버퍼를 문자열로 바꾼다

concat(배열) -  배열 안의 버퍼들을 하나로 합친다

 

버퍼가 readFile을 할 때 편리하긴 하지만 파일의 용량이 크고 많아지면 문제가 발생할 수 있습니다. 그래서 버퍼의 크기를 작게 만든 후 여러 번으로 나눠보내는 방식인 스트림이 등장했습니다.

 

2. 스트림(Stream)

스트림은 버퍼의 크기를 작게 만든 후 여러 번 나눠 보내는 방식입니다.

const fs = require('fs');

const readStream = fs.createReadStream('./readme3.txt', { highWaterMark: 16 });
const data = [];

readStream.on('data', (chunk) => {
  data.push(chunk);
  console.log('data :', chunk, chunk.length);
});

readStream.on('end', () => {
  console.log('end :', Buffer.concat(data).toString());
});

readStream.on('error', (err) => {
  console.log('error :', err);
});

출력 결과

createReadStream(파일 경로, 옵션 객체) - 파일 경로와 옵션 객체를 입력하면 읽기 스트림이 만들어 집니다. highwaterMark는 옵션 객체로 버퍼의 크기를 정할 수 있습니다.

readStream.on() - readStream은 이벤트 리스너를 붙여서 사용하는데 보통 data, end, error 이벤트를 사용합니다. data는 파일 읽기가 시작될 때 발생하고 end는 파일 읽기가 끝나면 발생하고 error는 파일 읽는 도중 에러가 발생하면 발생하는 이벤트 입니다.

 

 기타 fs 메서드 

  • fs.access(경로, 옵션, 콜백) - 폴더나 파일에 접근할 수 있는지 체크합니다. 옵션은 constants를 통해 가져오는데 F_OK는 파일 존재 여부, R_OK는 일기 권한 여부, W_OK는 쓰기 권한 여부를 체크합니다.
  • fs.mkdir(경로, 콜백) - 폴더 생성합니다.
  • fs.rmdir(경로, 콜백) - 폴더를 지웁니다.
  • fs.open(경로, 옵션, 콜백) - 파일의 아이디를 가져오는 메서드. 파일이 없다면 파일을 생성한 뒤 아이디를 가져옵니다.
  • fs.rename(기존 경로, 새 경로, 콜백) - 파일의 이름을 바꿔줍니다.
  • fs.unlink(경로, 콜백) - 파일을 지웁니다.
  • fs.copyFile(복사할 파일, 복사될 경로) - 파일을 복사하여 복사될 경로에 파일을 생성합니다.

 

이벤트

const EventEmitter = require('events');

const myEvent = new EventEmitter();
myEvent.addListener('event1', () => { // on == addListener
  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'); // 실행 안 됨

출력 결과

on(이벤트명, 콜백) - 이벤트 이름과 이벤트 발생시 콜백을 연결합니다. 이렇게 연결하는 것을 이벤트 리스닝이라고 합니다.

emit(이벤트명) - 이벤트를 호출합니다.

once(이벤트명, 콜백) - 한 번만 실행되는 이벤트입니다.

removeAllListeners(이벤트명) - 이벤트에 연결된 모든 이벤트 리스너를 제거합니다.

반응형