NodeJS를 위한 빠르고 개방적인 간결한 웹 프레임워크인 ExpressJS의 실습을 위한 간단한 개발환경 구축 및 실행방법에 대하여 정리해 보았습니다.


NodeJS 설치하기

Express를 위해서는 NodeJS가 우선적으로 설치가 되어 있어야 합니다. https://nodejs.org/en/ 에서 다운로드 받은 후 간단한 절차를 통해 설치가 가능합니다. terminal에서 node 버전확인을 통해 설치가 제대로 되었는지 알 수 있습니다.

$ node -v

node version: 6.10.2


프로젝트 생성하기

개발툴에서 ExpressJS 실습을 위한 프로젝트를 생성해줍니다. 저의 경우 visual studio code(vscode)에서 started_express 라는 이름의 폴더를 생성해 주었습니다. 개발툴은 자신에게 잘 맞는 것으로 사용하시면 됩니다.

Visual Studio Code다운로드: https://code.visualstudio.com/download


package.json 생성하기

NodeJS를 사용하는 프로젝트 진행시에 npm을 이용하여 필요한 모듈을 내려 받게 됩니다. 이 경우, 소스코드가 모듈에 의존하게 되면서 모듈 버전에 의한 문제가 생길수 있습니다. 실제로 이것은 빈번히 발생하는 문제입니다. 때문에 의존성 모듈에 대한 버전관리가 필수적인데, 그 역할을 JSON 형식으로 이루어진 package.json 파일이 할 수 있습니다. package.json은 모듈에 대한 의존성관리 뿐만 아니라 프로젝트에 관한 정보를 담고 repository 경로 정의 등의 역할을 합니다. 
 아래의 명령어를 통래 기본적인 package.json 파일을 생성할 수 있습니다. terminal에서 작업을 할 경우 해당 프로젝트 경로에서 수행을 하면되고  vscode를 사용할 경우 통합 터미널(ctrl+`)에서 수행해 줍니다.

$ npm init -y

npm init을 수행하게 되면 인터렉티브 프롬프트가 동작하면서 프로젝트에 대한 여러가지 정보를 사용자에게 묻게되고 사용자는 그에 대한 답을 입력 할 수 있습니다. 입력한 정보를 기반으로 파일이 생성됩니다. '-y' option을 추가하면 파일 생성시 사용자에게 응답받는 모든 항목들을 yes로 하여 생성하게 됩니다. 실제로는 프로젝트에 관한 정보를 입력해주는 것이 좋지만 간단한 환경구성을 위한 포스팅이므로 '-y'를 사용하여 package.json을 생성하도록 하겠습니다.

 

npm init 명령어 실행


 npm init -y 명령어 실행


생성된 package.json파일



ExpressJS 설치하기

$ npm install express --save

npm install express 명령을 수행하면 로컬모드로 node_modules폴더가 생성됨과 동시에 ExpressJS를 사용하기 위한 여러 모듈들이 node_modules 하위에 추가 됩니다. NodeJS에서는 require를 통해 해당 모듈을 등록하여 사용할 수 있습니다. '--save' 옵션을 사용하면 package.json의 dependencies항목에 해당 모듈이 추가 되는데 이 후 해당 모듈의 버전을 변경한 후 npm install 하게되면 npm이 dependencies에 추가된 모듈을 해당 버전에 맞도록 모두 재설치합니다. 이는 모듈에 대한 버전 정보를 확인 할 수 있을 뿐만 아니라 사용할 모듈의 버전의 변경 또한 간편히 할 수 있도록 합니다.

npm install express --save를 통해 package.json dependencies에  express 모듈이 추가된다. 


node_modules폴더 하위에 생성된 모듈

로컬 모드는 해당 프로젝트 경로를 의미 합니다. '-g' 옵션을 추가하여 글로벌 모드로 설치하게 되면 로컬 디렉토리가 아닌 시스템 디렉토리에 있는 node_modules에 모듈을 설치하게 되는데, 글로벌 모드로 설치된 모듈은 require를 사용할 수는 없고 아래와 같이 npm link 명령어를 입력해야 사용 할 수 있습니다. 글로벌 설치시에는 sudo를 사용하여 관리자 권한으로 수행해줍니다.

$ sudo npm install -g express

$ cd [local path]/[project]

$ npm link express



ExpressJS 실행하기

 Express를 실행하기에 앞서 테스트를 위한 index.js 파일을 생성하도록 합니다. vscode 에서 아래와 같이 파일생성 버튼을 눌러 생성할 수 있습니다.

 

terminal 또는 vscode 통합터미널에서 touch 명령어를 통해서 파일을 생성하는 방법도 있습니다.

$ touch index.js

생성된 index.js


 이후 index.js 파일에 Express 실행을 위한 가장 간단한 다음의 코드를 입력합니다.

var express = require('express');
var app = express();
 
app.get('/'function(req, res) {
    console.log('Hello Express!!');
});
 
app.listen(3000);
cs

NodeJS에서 기본적으로 제공하는 node 명령어에 실행할 파일명을 입력하고 수행한 후 http://localhost:3000 으로 접속하면 'Hello Express!!' 가 뜨는 것을 확인 할 수 있습니다. 3000 port로 listen 해주었기 때문에 http://localhost:3000 으로 접속한 모습입니다. ExpressJS에 관한 개념 또한 정리할 예정이니 향후 참조해 주시기 바랍니다.

$ node index.js

Express 실행



nodemon, supervisor 확장 모듈 설치 및 수행

위에서 사용했던 node를 사용한 웹서버 실행은 소스코드 수정이 있을때마다  ctrl + c로 서버를 종료하고 재수행 해주어야 브라우저에 반영이 되는 불편함이 있습니다. npm을 이용하여 supervisor나 nodemon 확장 모듈을 설치하여 사용하면 소스코드 수정을 자동으로 감지하여 매번 서버를 종료하고 재수행하는 수고를 하지 않아도 됩니다.

nodemon(node monitor)

$ sudo npm install -g nodemon

$ nodemon index.js

nodemon으로 서버 실행


supervisor

$ sudo npm install -g supervisor

$ supervisor index.js


참조

https://blog.outsider.ne.kr/665 :: package.json 으로 npm 의존성 모듈 관리하기
http://uiandwe.tistory.com/956 :: nodemon과 supervisor 모듈


신고

'NodeJS > ExpressJS' 카테고리의 다른 글

ExpressJS::개발환경 구축과 실행  (0) 2017.05.05




전통적인 callback 패턴

function mySandwich(param1, param2, callback) {
    alert('Started eating my sandwich.\n\nIt has: ' + param1 + ', ' + param2);
    callback();
}
 
mySandwich('ham''cheese'function() {
    alert('Finished eating my sandwich.');
});
cs

위와 같은 형식들자바스크립트에서 비동기 프로그래밍을 위한 패턴으로 callback을 사용했습니다. 하지만 이러한 전통적인 callback 패턴에는 몇가지 단점이 있습니다.

  • 비동기 처리 중에 발생한 오류를 예외처리 하기가 까다롭다. (http://blog.coderifleman.com/2014/11/15/javascript-and-async-error/ :: 비동기 에러처리의 어려움)

  • 여러 개의 비동기 로직을 한꺼번에 처리하는 데에 한계가 있다.

  • 코드의 복잡성이 증가하고 가독성이 떨어진다.


fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})
cs

fs 모듈을 사용한 파일을 처리에 관련된 소스코드입니다. 잦은 callback 함수의 사용으로 인해 소스코드가 직관적이지 않다는 것을 한눈에 알 수 있습니다. 모든 상황에 해당하는 것은 아니지만 몇몇의 특정 비동기적인 상황에서 그다지 유용한 패턴은아닙니다.


Promise란 무엇인가

비동기 프로그래밍을 위한 또 다른 패턴으로 Promise가 등장했고 Promise는 전통적인 callback 패턴이 가진 단점을 일부 보완( Promise가 callback이 가진 단점들에 대한 무조건적인 해결 방안이 되지는 않는다고 한다. )하여 비동기 처리 시점을 명확하게 표현합니다. Promise를 정의하면 비동기 처리 로직을 추상화 한 객체와 그것을 조작하는 방식이라고 할 수 있습니다. 이 개념은 E언어에서 처음 고안 되었으며 병렬 및 병행 프로그래밍을 위한 일종의 디자인 입니다. 이 디자인을 javascript 환경에 가져온 것이 Promise 입니다. Promise q async 같은 javascript library 제공되기 시작했으며 ES6 부터는 언어 레벨에서 제공하는 기능입니다상대적으로 가독성이 떨어지는 callback 패턴을 대체하여 async 함수도 sync 함수인 같은 flat 모양으로 코드를 작성하는 것이 장점입니다. Promise의 몇 가지 특징은 다음과 같습니다.

    • Promise를 지원하는 함수는 비동기 처리 로직을 추상화한 promise 객체를 반환한다.

    • 객체를 변수에 대입하고 성공 시 동작할 함수와 실패 시 동작할 함수를 등록해 사용한다.

    • 함수를 작성하는 방법은 promise 객체의 인터페이스에 의존한다. (promise 객체에서 제공하는 메서드만 사용해야 하므로 전통적인 callback 패턴처럼 인자가 자유롭게 결정되는 것이 아닌 같은 방식으로 통일된다.)

    • Promise라고 부르는 하나의 인터페이스를 이용해 다양한 비동기 처리 문제를 해경할 수 있다.(비동기 처리를 쉽게 패턴화 할 수 있다.) => Promise를 사용하는 큰 이유중 하나


Promise 살펴보기

Promise의 API, 워크플로우, 상태에 관하여 소개하며 Promise에 대한 개념을 익혀보려고합니다.


1. API

ES6 Promise 사양에서 정의하는 API는 일반적으로 크게 Constructor, Instance Method, Static Method 의 세 가지 종류가 있습니다.


Constructor

Promise는 XHR(XMLHttpRequest)처럼 생성자 함수에 new 연산자를 선언하여 promise 인스턴스 객체(promise 객체)를 생성해 사용합니다. Promise 객체를 생성하는 과정은 다음과 같습니다.

    1. new Promise(function)으로 promise 객체를 생성한다.

    2. function에는 비동기 처리를 작성한다.

      - 처리 결과가 정상이라면 resolve(결과 값)를 호출한다.

      - 처리 결과가 비정상 이라면 reject(error 객체)를 호출한다.

var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…
  // 비동기 처리 작성
 
  if (/* everything turned out fine */) {
   
  resolve("Stuff worked!");
  
} else {
    
reject(Error("It broke"));
  
}
});
cs

위에서 생성한 Promise는 then()을 호출하여 다음과 같은 방법으로 사용할 수 있습니다.

promise.then(function(result) {

  console.log(result); // "Stuff worked!"

}, function(err) {

  console.log(err); // Error: "It broke"

});
cs



Instance Method

new 연산자로 생성한 Promise의 인스턴스 객체에는 성공(resolve) 또는 실패(reject)했을 때 호출된 콜백 함수를 등록할 수 있는 promise.then()이라고 하는 인스턴스의 메서드가 있다.


promise.then(onFulfilled, onRejected)
cs

성공했을 때는 onFulfilled가 호출되며 실패했을 때는 onRejected가 호출된다. onFulfilled, onRejected는 선택적 (optional) 인자이므로 생략할 수 있다. 이처럼 promise.then()으로 성공 또는 실패 시의 동작을 동시에 등록할 수 있다. 만약 오류 처리만 하려면 promise.then(undefined, onRejected)와 같은 의미인 promise.catch(onRejected)를 사용하면 된다.


promise.catch(onRejected)
cs

Static Method

전역객체인 Promise에는 Promise.all()이나 Promise.resolve() 같은 정적 메서드가 있습니다. 이 메서드는 Promise를 다루는 데 필요한 보조 메서드입니다.



2. 워크플로우

function asyncFunction() {

    return new Promise(function (resolve, reject) {

        setTimeout(function () {

            resolve('Async Hello world');

        }, 16);
    });
}
 

asyncFunction().then(function (value) {

    console.log(value); // => 'Async Hello world'

}).catch(function (error) {

    console.log(error);

});
cs

    1. asyncFunction()은 Promise 생성자를 new 연산하여 promise 객체를 반환한다.
    2. 비동기 처리가 끝났을 때 호출될 callback을 promise 객체의 then()으로 등록한다. 
    3. 실패했을 때 호출될 callback은 catch()로 등록한다. setTimeout()으로 16ms 이후에 성공하도록 작성했으므로 16ms 이후 then()으로 등록한 callback이 호출되어 "Async Hello world"라는 메세지가 출력된다.
    4. 위의 경우 catch()로 등록한 callback은 호출되지 않는다. 하지만 setTimeout()이 존재하지 않는 환경이라면 예외가 발생하므로 catch()로 등록한 callback 함수가 호출된다.
    5.  catch()는 promise.then(onFulfilled, onRejected)로 대채하여 작성 할 수 있다.

asyncFunction().then(function (value) { 
console.log(value);

}, function (error) { 

console.log(error);

});
  cs



3. 상태

Promise 생성자 함수를 new 연산하여 생성한 Promise 객체에는 다음과 같은 세 가지 상태(State)가 존재합니다.  Promise 상태의 종류와 변화는 매우 단순합니다.


 ES6 Promise 사양에서의 명청

 Promise / A+ 에서의 명칭

 설

 "has-resolution"

 Fulfilled

  • 성공(resolve)했을 때의 상태
  • onFulfilled가 호출된다.

 "has-rejection"

 Rejected

  • 실패(reject)했을 때의 상태
  • onRejected가 호출된다.

 "unresolved"

 Pending

  • 성공도 실패도 아닌 상태
  • Promise 객체가 생성된 초기 상태를 말한다.

Promise 객체의 상태를 직접 조작할 일은 없으므로 이름 자체에 크게 신경쓰지 않아도 됩니다. 다음은 Promise/A+의 명칭인 Pending, Fulfilled, Rejected를 이용한 상태에 관한 설명입니다.


[[PromiseState]]

ECMAScript - DRAFT문서(https://tc39.github.io/ecma262/#sec-promise-objects)에 따르면 Promise의 상태는 [[PromiseState]]라고 하는 내부 정의에 의해 결정됩니다. 하지만 [[PromiseState]]에 접근할 수 있는 API가 없어서 기본적으로 알 수 없습니다.

Promise 객체는 Pending 상태로 시작해 Fulfilled나 Rejected상태가 되면 다시는 변화하지 않는다. 그래서 Fulfilled 및 Rejected 상태를 Settled(불변) 상태라고 합니다. 즉, Event 리스너와는 다르게 then()으로 등록한 callback 함수는 한 번만 호출됩니다. 정리하자면  then()은 promise 객체의 상태가 변화할 때 한번만 호출될 callback함수를 등록하는 메서드인 것입니다.


Settled 상태

성공 또는 실패 했을 때의 상태를 말합니다. Pending과 Settled는 서로 대응하는 관계라고 할 수 있습니다.




마치며

Promise에 대한 궁금증을 가지고 정리를 시작하였는데 생각보다 분량이 많고 요구하는 지식적인 깊이가 깊었습니다. 아직 Javascript의 코어적인 부분이 많이 부족해서인지 Promise의 정의나 간단한 문법과 사용법까지 정리를 해보았는데, Promise가 가진 구체적인 사용과 특징과 관련된 Promise에 대한 좀더 깊은 탐구는 추후에 다시 정리할 예정입니다.



살펴봐야할 문서

https://speakerdeck.com/kerrick/javascript-promises-thinking-sync-in-an-async-world :: JavaScript Promises–Thinking Sync in an Async Word ( Promise 상태변화 )




정리 하면서 참조한 문서

https://developers.google.com/web/fundamentals/getting-started/primers/promises :: 자바스크립트 프라미스:소개

http://callbackhell.com/ :: callback hell

http://www.sohamkamani.com/blog/2016/08/28/incremenal-tutorial-to-promises/ :: An incremental tutorial on promises in javascript

Hanbit eBook :: Javascript_promise

http://han41858.tistory.com/11 :: Promise를 사용하는 두 가지 방법, new Promise, Promise.resolve()


신고

'Javascript > Concept' 카테고리의 다른 글

Javascript::Promise에 대한 이해  (4) 2017.03.20
  1. 지나가던 이 2017.03.22 15:39 신고

    ㅋㅋ 블로그 주인장 멍청이~~

    • lqqo_kim 2017.03.22 16:08 신고

      도움이좀 되셨나요 방문해주셔서 감사합니다^^ 질책 감사합니다 ^^

  2. passing shawn 2017.04.17 13:48 신고

    Hi Your blog is looking good. It seems that the arrangement is a little short nowadays. Explain why. Is the company busy? Is there no time? Stop playing and studying. I always support you.

D3는 'Data-Driven Document' 라는 이름에서도 알 수 있듯이 다양한 방법으로 쉽게 Data를 DOM element에 쉽게 바인딩 할 수 있기 때문이다. 

 Data를 바인딩하여 출력물을 내는 간단한 예제를 통해서  D3의 기본적인 구조, selection 객체, select & selectAll 메소드 등을 알아본다.

현재 D3 v4가 release되었지만 v3, v4의 비교를 통한 공부를 하기위해 v3으로 진행한다.




기본개념에 대한 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
 
<head>
  <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
</head>
 
<body>
  <script>
    
    var theData = [1234 ,5];
    
    var selection = d3.select("body")
        .selectAll("p")
        .data(theData)
        .enter()
        .append("p")
        .text("Hello D3.js");
 
    console.log(selection);
 
  </script>
</body>
 
</html>
cs

결과


소스코드 분석


"body" 요소 선택  ->  존재하지 않는 요소("p")에 대한 빈 참조를 반환  ->  생성할 문서요소에 데이터 바인딩  ->  placeholder에 대한 참조반환  ->  DOM에 문서요소("p") 추가  -> 문서요소("p")에 텍스트 삽입

위 소스코드의 간략한 흐름은 위와 같다. 아래부터는 라인단위로 포함된 메소드에 대한 좀더 자세한 설명이다.

var theData = [1234 ,5];

  • DOM에 바인딩 하기위한 배열데이터를 선언한다.

var selection = d3.select("body")

d3

  • d3 글로벌 객체를 통해서 d3에 있는 메소드들을 사용할 수 있다.

select()

  • 메소드에 전달인자로 css 선택자를 주면 조건과 일치하는 첫번째 DOM 문서요소의 참조를 반환한다.
  • 매치되지 않으면 빈 selection 객체를 반환한다.
  • d3.select는 선택된 문서요소("body")를 원소로 하는 한 개의 group 배열(0: Array[1])을 원소로 하는 배열(Array[1])을 반환한다.

.selectAll("p")

selectAll()

  • 조건과 일치하는 여러 요소를 선택하고 참조를 반환한다.
  • 매치되지 않으면 빈 selection 객체를 반환한다.
  • 여러개의 문서요소("p")를 원소로하는 한 개의 group 배열(0: Array[0])을 원소로 하는 배열(Array[1])을 반환한다. 

선택된 "p"는 현재 존재하지 않기 때문에 group 배열 내부는 비어있다.

.data(theData)

data()

  • DOM 요소에 데이터를 바인딩(첨부, 매핑)한다. 
    • selection.data를 통해 최말단 문서요소("p")를 원소로 하는 group 배열에 조인한다. (selection.datum은 개별 최말단 문서요소에 직접 할당)
    • 데이터를 바인딩하기 위해서는 데이터와 DOM 문서요서 선택문의 두가지가 필수적으로 요구된다.
    • 빈 문서요소에 데이터를 연결 짓는다. 
  • data() 반환 값에 대해 enter(), exit() 메소드 사용가능.

data() 까지 진행했을 때 실직적으로 아직 template상의 변화는 없다. group 배열의 length가 증가한 것은 확인가능.

.enter()

enter()

  • selection에 바인드된 데이터들 중에 아직 실제 문서요소를 갖지 못 하는 것들을 찾아서 가상의 객체로 만들어 반환한다.
  • 신규 문서요소의 placeholder에 대한 참조를 반환한다. placeholder는 __data__ 속성만을 가지는 단순 객체이다.

append()를 하지 않아 실제 "p" 문서요소가 포함되지는 않았지만 __data__를 갖는 가상의 object 객체(placeholder)를 원소로하는 group 배열이 생성된 것을 확인 할 수 있다.

.append("p")

append()

  • 전달인자로 받은 DOM 문서요소를 새로 생성하여, 앞 체인에서 선택한 문서요소가 무엇이든 그 끝(내부)에 생성된 것을 추가한다.
  • 위와 같이 비어있는 d3 selection 객체를 선택하여 진행할 시에 selectAll() -> data() -> enter() -> append() 의 과정을 통하여 문서요소를 생성한다.

이전단계에서는 문서요소가 생성되지 않았지만 append()를 통하여  template 변화와 group 배열에 가상 객체가 아닌 실제의 객체가 생성된 것을 확인할 수 있다.


.text("Hello D3.js");

text()

  • 전달 인자로 문자열을 받아서 해당 선택된 문서요소의 여는 태그와 닫는 태그사이에 추가한다.
실제화된 "p" 문서요소의 참조를 전달 받았기 때문에 <p> 태그에 문자열이 위치하게 된다.


결론

각 operator들이 의미하는 바와 단계적으로 실행했을때의 객체와 배열의 변화를 직접 확인해보기 전까지 문서요소 생성시에 같은 요소를 입력받는 selectAll 과 append의 차이에 대해서 이해하기가 쉽지 않았다. 중요한 것은  존재하지 않는 DOM 요소를 생성할때에 selectAll은 해당 문서요소를 실제화 하지 못하고 selectAll() -> data() -> enter() -> append()의 과정속에서 최종적으로 append() 하였을때 문서요소가 실제화 된다는 것이다. 또한 메소드 체인을 통해 객체를 전달, 반환, 참조 전달 을 실행하는 d3의 흐름을 잘이해하기 위해 selection 객체의 변화를 잘 파악하는 것이 중요한 것 같다.


참조

http://blog.nacyot.com/articles/2015-02-02-d3-selection/ :: D3.js 기초 - select API와 enter() 이해하기


신고

'Visualization > D3Js' 카테고리의 다른 글

D3 :: select(), selectAll()을 이용한 문서요소 만들기  (2) 2017.03.15
  1. 2017.03.15 18:59

    비밀댓글입니다

    • lqqo_kim 2017.03.15 23:42 신고

      안녕하세요
      SQLite, mongoDB 등 db connection하여 사용 가능한 것 같습니다. d3js db connection 을 키워드로 검색하시면 확인해보실 수 있을 것 같습니다. ^^
      https://github.com/nyquist212/D3.js-Dash

      http://blog.vida.io/2016/11/06/connect-d3-dot-js-visualization-with-sql-database/

      원하시는 건진 모르겠지만 위 사이트도 한번 참조를..

+ Recent posts