NodeJS에서 제공하는 모든 api는 아래와 같이 3가지 형태로 제공한다.
예를들어 Node의 fs
제공하는 rename
메서드를 사용한다면 아래와 같이 3가지가 있다
fs.rename(..., callback(error, data));
비동기적(Non-Blocking)으로 작업을 처리후 callback이 실행된다. 일반적으로 콜백함수의 인자로 error
와 data
를 전달받는다.
예제
const fs = require('fs');
fs.rename('./text-new.txt', './text.txt', (error) => {
console.log(error);
});
console.log('hello');
fs.renameSync('./text.txt', './text-new.txt');
동기적(Blocking)으로 작업처리가 끝날 때까지 기다리다 비로소 다음 작업이 진행된다.
Node에서 제공해주는 API는 왠만하면 동기(sync)로는 사용하지 않는것이 좋다. 왜냐하면 비동기의 작업시점과 동기의 작업시점이 어떻게 맞물려서 동작하는지 추적하기 힘들기 때문이다.
예제
const fs = require('fs');
try {
fs.renameSync('./text.txt', './text-new.txt');
} catch (error) {
console.error(error);
}
동기적으로 동작하는 메서드는 위와 같이 try-catch
로 에러를 처리해준다.
에러가 발생할 수 있는 코드는 항상 에러처리를 해주어야 뒤의 작업들을 온전하게 진행할 수 있다.
fs.promise.rename()
프로미스에서 메서드 체인으로 사용하는 방식이다. 프로미스이기 대문에 비동기적으로 동작한다.
예제
const fs = require('fs');
fs.promises
.rename('./text2.txt', './text-new.txt') //
.then(() => console.log('Done!'))
.catch(console.error);
// text.txt
Hello!
const fs = require('fs').promises; // fs.promise를 가지고 온다.
// reading a file
fs.readFile('./text.txt')
.then(data => console.log(data))
.catch(console.error);
// 출력
// <Buffer 48 65 6c 6c 6f 21>
// 파일을 읽어올 때 2번째 인자로 인코딩에 아무런 값을 전달하지 않으면 버퍼로 읽어온다.
fs.readFile('./text.txt', 'utf8') // 파일을 utf-8로 인코딩하여 읽기
.then((data) => console.log(data))
.catch(console.error);
// 출력
// Hello!
// writing a file
// 파일에 글 작성하기
fs.writeFile('./file.txt', 'Hello, Dream Coders! :) ') //
.catch(console.error);
// 기존 파일에 글 추가하기
fs.appendFile('./file.txt', 'Yo!, Dream Coders! :) ') //
.catch(console.error);
// copy - 파일복사
fs.copyFile('./file.txt', './file2.txt') //
.catch(console.error);
// folder
// 폴더 생성
fs.mkdir('sub-folder') //
.catch(console.error);
// 폴더 정보 읽어오기, 폴더 내부에 있는 파일, 폴더들을 리스트 형태로 반환
fs.readdir('./') //
.then(console.log)
.catch(console.error);
// 출력
//
'app.js',
'file.txt',
'file2.txt',
'sub-folder',
'text.txt'
]
버퍼와 스트림은 파일을 읽거나 쓸 때 사용하는 방식이다.
용향이 큰 데이터를 전송할 때 한 번에 전송하지 않고 특정 단위 만큼 자르고 묶어서 전송을 하기 위해 사용되는 개념이다.
버퍼의 크기만큼 데이터를 순차적으로 버퍼에 담아서 전송한다.
버퍼는 숫자의 배열이고 메모리의 데이터를 가리키며 데이터의 바이트 그 자체이다.
버퍼를 하나씩 전송하는 중간 다리 역할이다.
잘개 나누어진 버퍼 데이터를 조금씩 보내준것을 스트리밍이라고 한다.
(이미지 출처: https://tcpschool.com/cpp/cpp_io_streamBuffer)
간단하게 말하면 버퍼는 데이터를 잘개 쪼갠 단위! 스트림은 버퍼를 전송하기 위한 연결통로 정도라고 생각하면 될 것 같다.
nodeJS에서 버퍼를 어떻게 사용하는지 알아보자.
const buf = Buffer.from('hello');
console.log(buf);
// <Buffer 68 65 6c 6c 6f>,
// 버퍼는 데이터가 있는 메모리를 가리킨다.
// 따라서 유니코드의 16진수 표기법의 형태로 hello가 표현되어있다.
console.log(buf.length); // 5
// 한 글자씩 출력할 경우 아스키코드의 10진수 표기법으로 표현된다.
console.log(buf[0]); // 104
console.log(buf[1]); // 101
console.log(buf.toString('utf-8')); // 기본값은 'utf-8'이므로 생략가능
// hello
// create - 버퍼 생성
const buf2 = Buffer.alloc(2); // 메모리에서 사이즈 2짜리 공간을 할당
const buf3 = Buffer.allocUnsafe(2);
// 메모리를 찾고 초기화 하지 않는 방식, 따라서 빠르지만 운이 없으면 이상한데이터가 미리 할당되어있을수도 있음
buf2[0] = 72;
buf2[1] = 105;
buf2.copy(buf3); // buf2 를 buf3으로 복사
console.log(buf2.toString()); // Hi
console.log(buf3.toString()); // Hi
// concat
const newBuf = Buffer.concat([buf, buf2, buf3]);
console.log(newBuf.toString());
// helloHiHi
const fs = require('fs');
const readStream = fs.createReadStream('./file.txt', {
highWaterMark: 8, // 스트림의 크기를 8바이트로 지정, 8글자씩 읽어온다. 기본값 64 kbytes
encoding: 'utf-8', // 기본값 'utf-8'
});
// data 이벤트 리스닝, 데이터가 읽어질 때 마다 발생
readStream.on('data', chunk => {
data.push(chunk);
});
// 종료 이벤트 리스닝, 딱 처음 한번만 end 이벤트를 캐치한다.
readStream.once('end', () => {
console.log(data.join(''));
});
// 에러 이벤트 리스닝
readStream.on('error', error => {
console.error(error);
});
// on은 자기 자신을 리턴한다. 따라서 체이닝해서 구현하면 가독성이 좋아진다.
const data = [];
fs.createReadStream('./file.txt', {
highWaterMark: 8,
encoding: 'utf-8',
}).on('data', chunk => {
data.push(chunk);
}).on('end', () => {
console.log(data.join(''));
}).on('error', error => {
console.error(error);
})
const writeStream = fs.createWriteStream('./text.txt');
writeStream.on('finish', () => {
console.log('finished!');
});
writeStream.write('hello!');
writeStream.write('world!');
writeStream.end();
readStream.pipe
메서드를 사용하면 여러 스트림끼리 파이프처럼 연결할 수 도 있다.
const fs = require('fs');
const zlib = require('zlib');
const readStream = fs.createReadStream('./file.txt');
const zlibStream = zlib.createGzip();
const writeStream = fs.createWriteStream('./result.zip');
const piping = readStream.pipe(zlibStream).pipe(writeStream);
piping.on('finish', () => {
console.log('done!!');
});