ECMAScript2015(ES6)로 Node.JS 코딩하기

Node.JS v4부터는 ES6를 지원하고 있다.

최근 아마존웹서비스(AWS)중 람다(Lambda)라는 서비스도 Node.js v4를 지원하기 시작했는데, 샘플코드를 보면 곳곳에 ES6 문법이 사용되어 있다. 순수 자바스크립트 차원에서 제공되는 기능들이 추가되었고 비교적 간결한 코드를 작성할 수 있는 것이 특징이다. 더 이상 새로운 자바스크립트 문법인 ES6 사용을 미룰 이유가 없는 것 같다.

최근 개발중인 프로젝트에 ES6 스펙을 사용한 경험을 바탕으로 ES5에서 ES6로의 변화에 대한 개인적인 느낌을 공유해 보고자 한다.

Promise

가장 마음에 드는 것은 프라미스(Promise)의 지원이다.

비동기 프로그래밍에서 콜백을 다루는 구조인 프라미스는 거의 필수 기능이다. ES5에서는 프라미스를 지원하지 않기 때문에 외부 라이브러리 사용이 불가피하다. Q를 가장 많이 사용했고 최근에 Bluebird를 사용해 봤다.

사실 이러한 라이브러리를 사용해도 개발하는데 문제는 없다. 하지만 언어 차원에서 지원하는 기능이 있다면 그것을 적극 활용하는 것이 개인적 취향이다.

Q와 Bluebird 모두 상당히 많은 기능을 지원하지만 몇 개의 함수만 사용할 뿐 아직도 모르는 것들이 많다. ES6 스펙은 그 정도로 많은 기능을 지원하지는 않지만 개발하기에는 충분해 보인다.

  • Promise()
  • Promise.resolve()
  • Promise.reject()
  • Promise.all()

사용법도 기존 라이브러리와 크게 다르지 않다.

new Promise((resolve, reject) => {
  if (condition) resolve((message: "success");
  else reject(new Error("fail"));
}).then(result => {
  console.log(result.message); // "success"
}).catch(err => {
  console.log(err.message); // "fail"
});

Arrow Function

위 코드를 보면 이상한 키워드가 보이지 않는가?

=>

그렇다. 이것을 Arrow Function이라고 하는데 커피스크립트의 ->과 같다. 기존의 function 키워드를 Arrow Function으로 대체하면 생각보다 코드량이 줄어든다. 이것은 함수형 프로그래밍을 작성할 때 효과적으로 사용할 수 있다.

ES5로 작성하면

User.findAll()
  .then(function (users) {
    return users;
  })
  .catch(function (error) {
    return Promise.reject(error);
  })

ES6로 작성하면 function 키워드를 =>로 대체할 수 있다.

User.finallAll()
  .then(users => {
    return users
  })
  .catch(error => {
    return Promise.reject(error)
  })

코드가 한 줄일 경우 블록 신택스({})와 return 키워드를 생략할수 있다.

User.findAll()
  .then(users => users)
  .catch(error => Promise.reject(error))

일곱 줄의 코드가 한 줄로 줄었다. 👏👏👏

User.findAll()
  .then(users => users)
  .catch(error => Promise.reject(error))

Rest Parameters

자바스크립트는 함수 파라메터를 명시적으로 넘기지 않아도 arguments 변수에 자동으로 할당한다. 그래서 함수로 넘어오는 파라매터의 갯수가 유동적일 경우 보통 이 arguments 변수를 이용해 파라매터 값을 배열로 만들어 사용한다.

function foo() {
  // 선언되어 있지않지만 arguments 변수를 이용해 배열을 만들 수 있다.
  console.log(Array.prototype.slice.call(arguments))
}

foo("bar", 123) // [ 'bar', 123 ]

그러나 Arrow Function을 사용하면 arguments변수를 사용할 수 없다. 대신 ES6는 Rest Parameter를 지원한다. 어떻게 사용하는지 아래 코드를 먼저 보고 설명하겠다.

const foo = (...args) => {
  console.log(args)
}

foo("bar", 123) // ['bar', 123]

...

이것이 Rest Parameter이다. 넘겨준 파라매터의 갯수에 상관없이 모두 배열에 담아 args 변수로 넘겨주기 때문에 기존의 배열 변환 코드가 필요없다.

Block Scoping

노드에는 module.exports 키워드가 있어서 모듈 내에서 사용하는 변수를 외부로부터 감출 수 있다. 하지만 브라우저에서 동작하는 자바스크립트는 그러한 안전 장치가 없기 때문에 항상 전역변수 사용에 대해 주의해야 한다. 전역변수로 노출시키지 않기 위해 주로 함수 스코핑을 사용한다.

(function() {
  var foo = 'bar';
})();

우선 변수하나 선언하는데 코드가 길어지고 자바스크립트 초심자에게는 괴상한 코드로 보이기도 한다.

ES6에서는 블록 스코프(Block Scope)를 지원한다. C, Java 같은 언어에서는 이미 기본적으로 제공하는 기능이기 때문에 이런게 기능인가 싶기도 하다. 그 동안의 자바스크립트는 우리에게 블록 스코핑이 얼마나 소중한 것인가를 절실히 깨닫게 해 주었다.

블록 스코프는 지역변수를 의미하는 let 키워드와 함께 사용한다.

let i = "abc"

for (let i = 0; i < 3; i++) {
  // for문의 i는 블록 바깥의 i와는 다른 변수다.
  console.log(i) // 0, 1, 2
}

console.log(i) // 'abc';

상수

보통 상수를 정의할 때 언더바(_)로 시작하거나 대문자를 사용한다. 이것은 순전히 개발자가 책임지고 코드이름을 보며 판단해야 하는 부담이 있다. 당연히 실수할 수 밖에 없고 버그로 이어진다.

ES6에서는 const라는 키워드로 상수를 정의한다. 이것도 너무나 당연한건데...... 여하튼 좋다.

Template Literals

문자열과 변수를 합치는(concat) 작업은 두 가지다.

+ 연산자로 합치거나 util.format() 함수를 사용하는 것.

이제는 템플릿 리터럴을 이용해 훨씬 간단하게 문자열을 만들수 있다. 이것도 커피 스크립트와 비슷한 기능이다.

let name = "Chris"
let message = `Hello ${name}!` // "Hello Chris!"

Class

솔직히 자비스크립트에서 클래스를 정의하고 상속까지 구현하는 것은 잘 안외워진다. 매번 검색해서 코드를 작성하는데 뭔가 꼼수같고 결국에서는 자바스크립트는 객체지향으로 코딩할 수 없는가? 함수형으로만 코딩해야하나? 라는 고민을 한다.

ES6에서는 class, extends 키워드를 사용해 간편하게 클래스를 정의하고 이를 상속하는 자식 클래스를 만들 수 있다.

class NotFoundError extends Error {
  constructor(message) {
    super(message || "Not Found")
    this.statusCode = 404
    this.errorCode = "NotFound"
  }
}

let error = new NotFounError("The user is not found")

클래스 몇 개만 사용한다면 이 정도 기능으로 충분할지도 모른다. 그러나 인터페이스 등 OOP 개발에 필요한 언어적 스펙이 없는 점은 아쉽다. 아마도 타입이 없는 자비스크립트의 특성과 연관있지 않나 생각한다.