Node.js 내장 모듈 - fs
오늘은 Node.js 내장 모듈인 fs에 대해 알아볼 것이다.
fs란 파일 시스템에 접근하는 모듈이다.
- 파일/폴더 생성, 삭제, 읽기, 쓰기 가능
- 웹 브라우저에서는 제한적이었으나 노드는 권한을 가지고 있음
파일 읽기 예제
readFile.js
const fs = require('fs');
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log(data);
console.log(data.toString());
});
콘솔 결과
data를 그대로 출력할 경우 버퍼 형식으로 데이터가 표현된다.
버퍼는 일련의 바이너리 데이터를 나타내는 데 사용되는 객체이다.
각각의 값은 16진수로 표현되며, 데이터의 바이트를 나타낸다.
사람이 읽을 수 있게 하려면 data.toString()을 하면 된다.
현재 경로에 있는 readme.txt 파일을 읽어온다.
콜백 함수의 늪에 빠질 수 있기 때문에 util.promisify를 통해 프로미스로 감싸줘서 사용하면 된다.
하지만, fs에서는 보다 더 효율적인 방법을 제공한다.
readFilePromise.js
const fs = require('fs').promises;
fs.readFile('./readme.txt')
.then((data) => {
console.log(data);
console.log(data.toString());
})
.catch((err) => {
console.error(err);
});
바로 require('fs')에 .promises만 붙이면 then/catch를 쓸 수 있다.
파일 생성 예제
writeFile.js
const fs = require('fs');
fs.writeFile('./writeme.txt', '글이 입력됩니다', (err) => {
if (err) {
throw err;
}
fs.readFile('./writeme.txt', (err, data) => {
if (err) {
throw err;
}
console.log(data.toString());
});
});
해당 파일을 실행할 경우 현재 경로에 writeme.txt 파일을 생성하고 읽어준다.
writeFile.js 또한 .promises를 붙여서 사용할 수 있다.
writeFilePromise.js
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);
});
프로미스나 콜백은 비동기 방식인데 순서대로 실행되지 않는 것을 아래 예제를 통해 확인할 수 있다.
async.js
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('끝');
async.js 코드를 보면 위에서부터 아래로 1번, 2번, 3번이 출력될 것 같다고 예상해볼 수 있다.
하지만 비동기 함수이기 때문에 아래와 같이 랜덤으로 출력된다.
콜백들은 백그라운드로 보낸다. 백그라운드로 넘어간 것들은 동시에 실행되기 때문에 완료되는 순서를 예측할 수가 없다.
순서를 보장해주지 않기 때문에 실제 서비스를 개발할 때 문제가 생길 수 있다.
순서를 보장하기 위해서 여러 방법을 사용할 수 있다.
1. 동기 메소드 사용
fs 모듈은 동기 메소드도 지원해준다. 동기이기 때문에 처리 속도는 비동기보다 비효율적이다.
동기 메소드를 사용하는 경우는 딱 한번 실행하거나 서버 초기화 작업을 할 경우에 사용한다.
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('끝');
콘솔 결과
2. 콜백 안의 콜백(= 콜백 헬)
asyncOrder.js
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('끝');
});
});
});
이 방식은 소위 말하는 콜백 헬에 빠질 수 있기 때문에 추천하지 않는다.
3. 프로미스
asyncOrderPromise.js
const fs = require('fs').promises;
console.log('시작');
fs.readFile('./readme2.txt')
.then((data) => {
console.log('1번', data.toString());
return fs.readFile('./readme2.txt');
})
.then((data) => {
console.log('2번', data.toString());
return fs.readFile('./readme2.txt');
})
.then((data) => {
console.log('3번', data.toString());
console.log('끝');
})
.catch((err) => {
console.error(err);
});
2번에서 콜백 헬 문제를 해결한 방식이다. promises로 감싸줘서 then/catch를 사용한다.
이 방식도 then을 연달아 사용하면 가독성이 떨어지기 때문에 더 깔끔하게 만들고 싶으면 4번 방식을 사용하면 된다.
4. async/await
asyncAwait.js
const fs = require('fs').promises;
async function main() {
console.log('시작');
let data = await fs.readFile('./readme.txt');
console.log('1번', data.toString());
data = await fs.readFile('./readme.txt');
console.log('2번', data.toString());
data = await fs.readFile('./readme.txt');
console.log('3번', data.toString());
console.log('끝');
}
main();
async/await을 사용하면 1번 동기 메소드를 사용한 것처럼 깔끔하게 코드를 작성할 수 있다.