[Node] Essential Node.js 1

Node.js 에센셜 1 (REPL, Module, Global, FS)


목차

  • Node와 직접 대화하고 싶어! - REPL
  • 노출과 공유를 제어하자 - Module 시스템
  • 동기와 비동기를 조심하자 - File System Module
  • 내가 어디에 있든 네가 필요해! - Global Object


들어가며

새로운 것을 배울 때 사용 방법을 숙지하는 것 못지 않게 중요한 것은 ‘해서는 안될 것’을 먼저 익히고 피하는 것이다. 사용법 숙지는 익숙함과 시간이 해결해 주지만, 잘못된 습관은 시간이 지날수록 고치기 힘들기 때문이다.

그래서 공식 문서는 무엇보다 중요하다. 배움의 효율성보다 ‘해서는 안될 것’을 명확히 인지하고 ‘올바른 사용 방법’을 익히는 가장 확실한 방법이기 때문이다. 특히 Node.js는 싱글 스레드 기반의 논블로킹 IO와 비동기 처리라는 장점이 명확한 만큼, 특징을 명확히 이해하고 올바른 용례를 익히는 것은 매우 중요하다.

Node.js 입문자로서 올바른 사용법을 숙지하고 실수를 피하고자 공식 API DOC의 필수적인 내용을 정리했다.


node와 직접 대화하고 싶어! - REPL

REPL이란 다음과 같이 사용자가 커맨드를 입력하면 시스템이 값을 반환하는 환경을 뜻한다

  • Read − 유저 입력을 읽고, 해석하고, 저장하고
  • Eval − 결과를 평가하고 처리하고
  • Print − 결과를 출력하고
  • Loop − 위 과정을 종료할 때까지 반복한다

터미널에서 node 를 실행하면 REPL이 실행된다. 연산자 지원과 변수 사용, multi-line 표현식, underscore(_) 변수 등을 지원한다.

$ node
> 

// 연산자 지원
> 2
2
> 2 + 5
7

// 변수 사용
> const x = 2
undefined
> const y = 5
undefined
> x + y
7

// multi-line 표현식
> for (let i=0; i<3; i++){
... console.log(i)
... }
0
1
2

// underscore 변수 (최근 결과값을 저장)
> 5
5
> console.log(_)
5
> typeof _
'number'
> console.log(_)
number

REPL 환경을 조작하는 기본 comnmand는 다음과 같다

  • Ctrl+C – 현재 명령어 종료
  • Ctrl+C (2번) – Node REPL 종료
  • Ctrl+D – Node REPL 종료
  • 위/아래 키 – 명령어 히스토리를 탐색, 이전 명령어를 수정
  • Tab – 현재 입력란에 쓴 값으로 시작하는 명령어 / 변수 목록을 확인
  • .help – 커맨드 목록을 확인
  • .break – 멀티 라인 표현식 입력 도중 입력을 종료
  • .clear – .break 와 동일
  • .save filename – 현재 Node REPL 세션을 파일로 저장
  • .load filename – Node REPL 세션을 파일에서 로드


노출과 공유의 범위를 제어하자 - Module 시스템

웹브라우저에서 JS에서 전역 변수를 선언한다면 이는 모두 window 전역 객체의 프로퍼티로 간주된다. 변수가 서로 공유될 수 있는 환경에서 변수 이름이 같다면 변수가 중복이 되는 등 여러가지 문제가 생길 것이다.

Node.js에서 하나의 파일은 하나의 모듈이다. 전역의 문제를 해결하기 위해 Node.js 에서는 하나의 파일을 하나의 모듈로 1 대 1 대응시키고, 각 모듈을 함수로 한 번 더 wrapping해 독립적인 스코프를 가지게 한다. 모듈에 선언된 모든 것들은 private으로 간주되며, 내부에서만 참조가 가능하다.

기본적인 사용법은 모듈을 파일로 작성한 후, 외부에 공개하고 싶은 대상을 module.exports 객체에 할당하고, require() 메서드를 이용해 추출해 public처럼 사용한다.

module.exports

지정한 모듈을 특정 클래스의 인스턴스로 할당해 사용할 수 있다. 할당받은 즉시 실행되므로 callback에서는 작동하지 않는다. module.exports는 빈 객체 {} 로 초기화되어 있고, 빈 객체 대신 사용할 메서드나 프로퍼티를 할당한다.

// hello.js
const sayHello = () => {
    console.log('hello');
}
module.exports = sayHello;

exports와 어떤 차이가 있나요?

exports 는 module.exports를 call by reference로 참조하고 있고, 항상 module.exports를 반환한다. exports가 exports 객체에 프로퍼티를 추가하는 개념이라면, module.exports는 새로운 객체를 할당하는 개념이다.

module.exports = exports = function Constructor() {
  // ... etc.
};

만약 exports에 다른 객체를 할당하게 된다면, 더 이상 module.exports를 참조하지 않게 된다. 다음의 경우는 모두 틀린 용례이다.

exports = {foo : "bar"}                              
exports = function() { console.log("foo") }          

module.exports에 값을 할당하는 것이 아닌, exports 변수에 새로운 값을 할당한 것이므로 module.exports에 반영되지 않는다. 실수의 여지가 있으므로, exports를 쓸 경우 늘 주의해야 한다.

require(id)

Node.js 내부의 module.js 안에 정의된 메서드로서 파라미터로 받은 path에 위치한 파일을 불러온다.

const name = require('파일경로')

require(id) 함수는 먼저 보호하고자 하는 API를 function expression으로 wrapping한 다음, module.exports 객체를 return 하게 된다. 즉시 실행 함수이므로 만약 전역 메서드가 있다면 그대로 실행하게 된다. 그래서 전역에 무언가를 직접 선언하는 일은 피하는 것이 side effect를 통제할 수 있는 유일한 방법이다.

// foo.js
const sayHello = () => {
  console.log('hello, node!')
}
console.log('hello, global!');
module.exports = sayHello;

// bar.js
require('./foo.js')

// result : hello, global!
// module.exports에 할당받은 sayHello 함수를 실행시키지 않고, 단순히 모듈만 require 했지만
// 내부에서 즉시실행함수로 실행되므로 전역에 선언된 'hello, global!'이 출력된다

주의사항

  • 한 번 require된 모듈은 require.cache라는 객체에 캐싱되어 계속 재사용 하게 된다. 캐싱될 때 파일 경로를 key로 저장하게 되는데, Node.js에서는 대소문자를 구별해 별도의 모듈로서 취급한다. (foo.js !== Foo.js) 하지만 윈도우나 mac같이 파일시스템의 대소문자를 구별하지 않는 운영체제에는 동일한 모듈로 취급되니 주의해야 한다.
  • 순환 require가 있을 때 실행을 완료하지 못할 수도 있으니, 의존 관계를 주의하자.


동기와 비동기를 조심하자! - File System Module

const fs = require('fs');

fs는 파일 시스템과 상호작용하기 위한 API를 제공한다. 가장 중요한 포인트는 fs 모듈에 있는 모든 명령어는 동기와 비동기 방식으로 구분된다는 점이다. 동기는 작업이 완료될 때까지 프로세스가 멈추지만, 비동기는 이벤트 루프에 메시지 처리를 위임하기 때문에 속도가 훨씬 빠르다. 때문에 특별한 경우를 제외하고 비동기를 쓰는 것이 일반적이다.

async

빠르지만 순서는 보장되지 않아요

비동기는 실행 순서가 보장되지 않음을 다시 한 번 상기하자. 아래의 예시는 순서가 보장되지 않는 비동기의 특성 상 fs.stat()이 실행되기 전 fs.rename()이 완료되지 않을 수 있기 때문에 오류의 가능성이 존재한다.

fs.rename('/tmp/hello', '/tmp/world', (err) => {
  if (err) throw err;
  console.log('renamed complete');
});
fs.stat('/tmp/world', (err, stats) => {
  if (err) throw err;
  console.log(`stats: ${JSON.stringify(stats)}`);
});

callback을 사용합시다

fs.rename() 가 실행된 후, fs.stat()을 콜백으로 받아 순차적으로 실행되어 오류가 나지 않는다.

fs.rename('/tmp/hello', '/tmp/world', (err) => {
  if (err) throw err;
  fs.stat('/tmp/world', (err, stats) => {
    if (err) throw err;
    console.log(`stats: ${JSON.stringify(stats)}`);
  });
});

예외 역시 callback으로 처리합니다

비동기 방식은 항상 마지막 인자는 완료 callback을 받는데, 이 완료 callback의 첫번째 인자는 예외를 처리하는 error 객체로 항상 예약되어 있다. 작업이 성공적으로 완료된다면 callback의 첫번째 인자는 null이나 undefined가 된다.

const fs = require('fs');

fs.unlink('/tmp/hello', (err) => {
  if (err) throw err;
  console.log('successfully deleted /tmp/hello');
});

sync

예외가 발생하면 즉시 throw 되고, try/catch 블럭을 이용하여 처리한다.

const fs = require('fs');

try {
  fs.unlinkSync('/tmp/hello');
  console.log('successfully deleted /tmp/hello');
} catch (err) {
  // handle the error
}


내가 어디에 있든 네가 필요해! - Global Objects

모든 모듈에서 공통적으로 쓸 수 있는 객체다. 모듈의 종류에 상관없이 범용적으로 쓰이는 객체를 Global object로 지정해 어떤 모듈에서든 접근해서 쓸 수 있도록 허용한다. 표준입출력을 담당하는 console, 프로세스 정보를 제공하거나 조작하는 process 의 경우가 대표적인 예다.

process

가장 최신의 Node.js 프로세스 정보를 제공하거나 통제할 수 있는 객체. 많은 메서드들과 프로퍼티들이 컴퓨터 시스템이나 운영체제와 밀접한 연관이 있는 정보를 제공하거나 조작한다. 웹 브라우저에는 존재하지 않는다.

 // Property
 process.argv  						// 실행 매개 변수
 process.env 						// 컴퓨터 환경과 관련된 정보
 process.version 					// 버전
 process.versions 					// Node.js와 종속된 프로그램 버전
 process.arch 						// 프로세서의 아키텍처
 process.platform 					// 플랫폼

 // Method
 process.exit([exitCode = 0]) 		// 프로그램을 종료(매개변수 생략가능)
 process.memoryUsage() 				// 메모리 사용 정보 객체를 리턴
 process.uptime() 					// 현재 프로그램이 실행된 시간을 리턴

console

표준 입출력을 담당하는 객체. 웹 브라우저에서 제공하는 것과 유사한 기능을 제공한다.

console.assert(true, 'this is exist'); 			// true/false 검사
console.clear(); 								// 화면 clear
console.dir();									// 객체 정보
console.error();								// 에러 정보 출력
console.count();								// 호출 count 로깅

주의사항

global Object처럼 보이지만, 다음 프로퍼티와 메서드는 module scope을 따르니 주의하자.

__ dirname, __filename, exports, module, require()

이 외 setInterval() 등 우리가 비동기 예제에서 많이 봐왔던 timer의 메서드, url 파싱과 관련된 기능들을 모아놓은 url 등 다양한 global object가 있으니 공식 문서에서 차근차근 살펴보자.


다음편

  • HTTP Module
  • Stream Module
  • Path Module
  • Events Module
  • Error handling


Reference

 Date: May 22, 2019
 Tags:  NODE

Previous:
⏪ 20190520 TIL

Next:
[Algorithm] Insertion Sort ⏩