파일 시스템 접근이란 파일/폴더의 생성, 삭제, 읽기, 쓰기가 가능하다는 것
아무거나.txt 생성 -> 텍스트 저장
같은 폴더에 read.js 생성
const fs = require('fs');
fs.readFile('./아무거나.txt', (err, data) => {
if(err) {
throw eff;
}
console.log(data);
console.log(data.toString());
});
node read 실행 시 버퍼 값(data)과, 아무거나.txt 파일의 텍스트 내용이 나온다.fs는 콜백 형식 모듈이니 사용하기 편하게 프로미스 형식으로 바꿔준다. 프로미스는 비동기 코드를 다루기에 에러 처리가 편리하고, 코드의 가독성을 높여준다.
const fs = require('fs').promises;
fs.readFile('./아무거나.txt')
.then((data) => { //then, catch문으로 에러 처리 따로 빼기
console.log(data);
console.log(data.toString());
}).catch((err) => {
console.log(err);
});
const fs = reauire('fs').promises;
fs.writeFile('./writeme.txt', '글이 입력됩니다')
.then(() => {
return fs.readFile('./writeme.txt');
}).then((data) => {
console.log(data.toString());
}).catch((err) => {
console.error(err);
});
노드는 기본적으로 비동기 방식으로 메소드를 처리하지만, fs 모듈이 가진 몇몇 메소드는 동기 방식으로 사용한다.
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('끝');
실행 시 시작, 끝을 제외하면 번호의 출력 순서는 다 다르며, 실행할 때마다 계속 결과가 달라진다.
I/O 요청이 많이 들어와도 메인 스레드는 요청 처리를 백그라운드에게 위임하기 때문에 요청을 더 받아도 상관없다. 백그라운드가 처리 후 메인 스레드에게 알리면 메인은 그때 콜백 함수를 처리한다. -> 서버 같이 요청 수를 예측하기 어려울 때 사용하기 적합
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('끝');
동기 메소드인 readFileSync를 사용하면 코드는 직관적이지만, 요청이 많이 들어오면 readFile만큼 완벽히 요청을 처리하지 못한다. 이전 작업이 완료 되어야 다음 작업 시작이 가능하기 때문이다.
백그라운드에서 열심히 작업을 처리하는 동안 메인 스레드는 백수... 비효율적이다. -> 프로그램 처음 실행할 때 초기화 용도로 적합
ex) readFileSync는 자바스크립트로 백준 문제 풀 때 사용함
const fs = require('fs');
console.log('시작');
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());
fs.readFile('./readme2.txt', (err, data) => {
if (err) {
throw err;
}
console.log('1번', data.toString());
});
});
});
이전 readFile의 콜백에 다음 readFile을 넣는다.
const fs = require('fs').promises;
console.log('시작');
fs.readFile('./readme2.txt')
.then((data) => {
console.log('1번', data.toString());
return fs.readFile('./readme2.txt');
})
// ...(2번 생략)...
.then((data) => {
console.log('3번', data.toString());
console.log('끝');
})
.catch((err) => {
console.error(err);
});
왜 아까부터
data뒤에toString()을 붙이나요?data를 그대로 출력하면 안 되나요?
노드가 파일을 읽을 때 메모리에 파일 크기만큼 공간을 마련하고, 파일 데이터를 메모리에 저장한다. 버퍼는 이 메모리에 저장된 데이터이다. 우리가 코드에 쓰는 data는 이 버퍼를 출력시키는 것이다.
그런데 날것 그대로의 버퍼는 사용자가 읽기 몹시 불편한 형태를 가지고 있기 때문에 toString()으로 문자화를 시켜야 한다.
readFile 방식의 버퍼를 쓰면 편하긴 하지만, 100MB 파일을 읽을 때 메모리에 똑같이 100MB 버퍼 만들어야 한다. 서버에 이를 적용한다면, 10번의 요청이 들어올 경우 약 1GB의 메모리가 사용된다. 버퍼 대신 버퍼의 크기를 작게 만든 후 여러 번으로 나눠 보내는 스트림을 사용한다면 메모리 문제를 줄일 수 있다. 스트림은 100MB짜리 파일을 읽을 때 1MB 크기의 버퍼를 생성하고, 파일을 백 번 나눠서 읽는다.
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);
})
fs.access(경로, 옵션, 콜백): 폴더나 파일 접근 가능 체크fs.mkdir(경로, 콜백): 폴더 생성 fs.open(경로, 옵션, 콜백): 파일 아이디 가져오는 메서드. 아이디로 read/write 가능. 만약 파일 아이디를 가져오는데 파일이 없다면? 옵션 w로 하면 파일 없을 때 새로 만듦
fs.rename(기존 경로, 새 경로, 콜백): 파일 이름 수정. fs.readdir(경로, 콜백): 폴더 안의 내용 확인fs.unlink(경로, 콜백): 파일 삭제fs.rmdir(경로, 콜백): 폴더 삭제fs.copyFile(복사할 파일, 파일 경로, 콜백): 파일 복사const fs = require('fs');
fs.watch('./target.txt', (eventType, filename) => { //target.txt는 비어있음
console.log(eventType, filename);
});
node로 파일 실행 후 target.txt 수정 시, 콘솔 창에 change 이벤트 발생한다. 파일명이 변경되거나 삭제된 경우에는 watch 메소드가 더이상 실행되지 않는다.
fs 모듈을 포함한 여러 비동기 메서드들은 실행 시 백그라운드에서 실행되고, 실행이 끝나면 메인 스레드의 콜백 함수나 프로미스의 then 부분을 실행한다.
비동기 메서드를 여러 번 실행해도 백그라운드에서 동시에 처리될 수 있는 이유는 스레드풀이 있기 때문이다. fs 모듈은 내부에 스레드풀을 사용하는데, 이 외에도 내부적으로 스레드풀을 사용하는 모듈은 crypto(암호화 관련 모듈), zlib(파일 압출 관련 모듈), dns.lookup(DNS 조회 관련 모듈) 등이 있다.
3.6.2에서 비동기 메서드로 처리했을 때 번호 순서가 불규칙하게 나왔던 것이 바로 스레드풀이 작업을 동시에 처리하기 때문이다. 스레드풀의 개수에 따라 출력이 조절되는데, 스레드풀이 1개라면 비동기식이어도 순서대로 출력된다. 왜냐하면 작업이 한 번에 하나씩 처리되기 때문이다.