[Node.js] util모듈, fs모듈, 버퍼와 스트림, events모듈, 예외처리

노드 기능 알아보기 (3)

util 모듈

const util = require('util');
const crypto = require('crypto');

/* 1. util.deprecate() */
// deprecate()는 지원지 조만간 중단될 메서드임을 알려줄 때 사용한다.
const dontuseme = util.deprecate((x, y) => {
    console.log(x + y);
}, '이 함수는 2018년 12월 부로 지원하지 않습니다.');

dontuseme(1, 2)	// 3, DeprecationWarning: 이 함수는 2018년 12월 부로 지원하지 않습니다.

/* 2. util.promisify() */
// promise를 지원하지 않는 (error, data) => {} 꼴의 콜백은 util.promisify로 프로미스로 만들 수 있다.

const randomBytesPromise = util.promisify(crypto.randomBytes);
const pbkdf2Promise = util.promisify(crypto.pbkdf2);

// 콜백을 프로미스로 바꿔서 사용할 수 있다.
randomBytesPromise(...).then((...) => {}).then((...) => {}).catch((err) => {});

// 프로미스를 반환하는 함수에 대해서 async, await 사용 가능하다.
(async () => {
    const buf = await randomBytesPromise(...);
})();

fs 모듈 (동기와 비동기)

const fs = require('fs');
// fs 메서드는 비동기 방식으로 작동한다.

// 파일 읽기
fs.readFile('./readme.txt', (err, data) => {
    if(err) {
        throw err;
    } 
    console.log(data);	// buffer가 리턴된다.
    console.log(data.toString());	// 원래 데이터가 리턴된다.
});

// 파일 쓰기
fs.writeFile('./writeme.txt', '글을 써주세요', (err) => {
    if(err) {
        throw err;
    }
    fs.readFile('./writeme.txt', (err, data) => {
        if(err) {
            throw err;
        }
        console.log(data.toString());
    })
});

// 비동기 방식으로 작동하는 fs 메서드를 동기 방식으로 작동시킬려면
// 1. 콜백으로 계속 연결시킨다. (콜백 지옥 ㅠ..)
// 2. readFileSync() 메서드로 바꾼다.
fs.readFileSync('./readme.txt');

버퍼와 스트림

버퍼는 컴퓨터가 데이터를 조각조각 떼어서 읽는 저장소, 저장소에 데이터를 채우는 과정을 버퍼링이라고 하고, 버퍼에 조각난 데이터가 꽉 채워지면 전송된다.

스트림은 내용물이 꽉 찬 버퍼를 전송하고, 또 버퍼에 데이터를 채워서 전송하는 식으로 연속해서 데이터가 담긴 버퍼를 전송하는 것이다.

const fs = require('fs');

/* 파일 읽기 */

// 스트림 방식 (버퍼를 채우면 읽어들이고 다음 버퍼를 채운다.)
const readStream = fs.createReadStream('./readme.txt', {highWaterMark: 16});	// readme.txt 파일을 16byte씩 읽어들인다.
const data = [];

// 스트림은 이벤트 기반으로 동작한다. data, end, error 등의 이벤트가 있다.
// 버퍼(청크)들이 들어올 때마다 data 이벤트가 발생한다.
readStream.on('data', (chunk) => {
    data.push(chunk);
    console.log('data', chunk, chunk.length);
});

// 다 끝났으면,
readStream.on('end', () => {
    // Buffer는 node global 객체이다. data 배열에 가득한 청크들을 하나로 합쳐서 사람이 읽을 수 있도록 해준다.
    console.log('end', Buffer.concat(data).toString()); 
})

//  에러가 발생했을 경우
readStream.on('error', (err) => {
    console.log('error', err);
})

/* 파일 쓰기 */
const writeStream = fs.createWriteStream('./writeme2.txt');

// 쓰기 완료 했을 때 이벤트
writeStream.on('finish', () => {
  console.log('파일 쓰기 완료')
})

// 쓰고싶은만큼 write 한다.
writeStream.write('이 글을 씁니다.\n');
writeStream.write('한번 더 씁니다.');
// 끝낼 땐 end.
writeStream.end();

스트림은 버퍼의 흐름이기 때문에 여러개의 스트림을 이어 버퍼가 흘러가게 할 수 있다.

const fs = require('fs');

// readme4.txt를 읽어서 그 내용을 writeme3.txt에 쓴다. (복사랑 비슷)
const readStream = fs.createReadStream('readme4.txt');
const writeStream = fs.createWriteStream('writeme3.txt');

readStream.pipe(writeStream);

// 노드에서 파일을 복사하는 최신 방법 (pipe로 연결하지 않아도 가능하다.)
const readStream = fs.copyFile('./readme4.txt', './writeme4.txt', (err) => {
    console.log(err);
})

// readme4.txt 파일을 읽어서 그 내용을 압축해서 쓸 때
const zlib = require('zlib');
const zlibStream = zlib.createGzip();

readStream.pipe(zlibStream).pipe(writeStream);

events 모듈

// 이벤트를 만들기 위해 모듈을 불러온다.
const EventEmitter = require('events');

// 커스텀 이벤트를 만드는 객체
const myEvent = new EventEmitter();

myEvent.addListener('방문', () => {
    console.log('방문해주셔서 감사합니다.');
})

// .addListenear() 메서드는 .on() 메서드로 대체할 수 있다.
// 종료 이벤트가 발생했을 때 console이 두번 찍힌다.
myEvent.on('종료', () => {
    console.log('안녕히가세요.');
})
myEvent.on('종료', () => {
    console.log('제발 좀 가세요');
})

// 한 번만 실행되는 이벤트
myEvent.once('특별이벤트', () => {
    console.log('한 번만 실행됩니다.');
})

// 이벤트를 호출한다. (콜백이 실행된다.)
myEvent.emit('방문');
myEvent.emit('종료');
myEvent.emit('특별이벤트');	// 한 번만 실행된다.
myEvent.emit('특별이벤트');	// 실행되지 않음.

myEvent.on('계속', () => {
    console.log('계속 리스닝');
})

// 이벤트 리스너 해제
myEvent.removeAllListeners('계속');	// 하나의 이벤트에 여러 개의 리스너가 붙을 수 있기 때문에 removeAllListeners()를 사용한다.

// 여러개의 같은 이벤트 중 하나의 리스너만 해제하려면...
const callback = () => {
    console.log('제발 좀 가세요');
}

myEvent.on('종료1', () => {
    console.log('안녕히가세요.');
})
myEvent.on('종료1', callback);

// (이벤트이름, 리스너콜백) 순으로 넣어주면 해당 리스너만 해제된다.
myEvent.removeListener('종료1', callback);

// 이벤트 리스너가 몇 개 달려있는지 확인
console.log(myEvent.listenerCount('종료1'));

예외 처리

예외를 처리하지 못해서 에러가 발생하면 노드 프로그램이 죽어버리기 때문에 항상 처리해줘야 한다.

멀티쓰레드인 경우에는 쓰레드 하나가 죽어도 다른 쓰레드가 그 일을 대신할 수 있지만, 노드의 경우에는 싱글쓰레드이기 때문에 하나만 죽어도 다 죽기 때문에 예외 처리가 매우 중요하다고 볼 수 있다.

setInterval(() => {
    console.log('시작');
    try {
    	throw new Error('서버를 고장내주마');    
    } catch(err) {
        console.error(err);
    }
}, 1000);

try {…} catch {…} 는 권장하는 방법은 아니다. 에러가 발생하면 에러메세지를 띄우기 보다는 바로 해결하는 것이 좋다.

** 노드 내장 메서드는 실행되다가 에러가 발생해도 프로그램이 멈추지 않기 때문에 따로 예외 처리를 신경쓰지는 않아도 된다. (console로 기록만 해두고 나중에 해결해도 된다.)

/* try~catch 안쓰고 에러 해결하는 방법 */

// uncaughtException을 사용하면 처리되지 않은 에러가 발생해도 이후 코드가 죽지않고 실행된다.
// 대부분의 에러는 잡아낼 수 있고, 기록되지만, 에러가 해결된 것은 아니기 때문에 근본적인 원인을 해결하는 것이 더 중요하다.
process.on('uncaughtException', (err) => {
    console.error('예기치 못한 에러', err);
    // 서버를 복구하는 코드 - 노드에서는 uncaughtExceptiondml 콜백이 실행될지 안될지 보장되지 않기 때문에 콜백에서 서버를 복구하는 코드를 사용하지 않는 것이 좋다.
})

setInterval(() => {
    throw new Error('서버를 고장내주마!');
}, 1000);

setTimeout(() => {
    console.log('실행됩니다');
}, 2000);

댓글남기기