반응형

 노드는 기본적으로 단일 쓰레드 기반으로 동작한다. 이벤트 루프를 통해서 비동기적으로 동작하기 때문에 멀티 쓰레드로 동작하는 것처럼 착각을 일으킬 수 있다. 자바스크립트에서는 당장 처리하지 못하는 작업을 여러 큐에 관리하는데, 이벤트, 타이머, 인터벌, 즉시 실행 큐등이 있다. 자바스크립트에서는 멀티 쓰레드가 아니기 때문에 어떤 한 부분에서 CPU를 독점하면 프로그램이 Block 된다. 

setInterval(function(){

        console.log('10 Interval Task');

},10);

setInterval(function(){

        console.log('20 Interval Task');

},20); 


setInterval(function(){

        console.log('10 Interval Task');

},10);

setInterval(function(){

        while(true);

        console.log('20 Interval Task');

},20); 

 위 코드에서 왼쪽코드는 "10 Interval Task" 2번에 "20 Interval Task"가 한번씩 출력되지만 오른쪽 코드는 "10 Interval Task"가 한번 출력되고 프로그램이 블락된다. 이는 while(true)가 CPU 를 점유하기 때문이다. 노드는 비동기식 I/O 를 위하여 설계되었다. I/O는 엄청 느리기 때문에 대다수 동기식 I/O를 지원하는 언어들은 멀티쓰레드를 지원한다. 비동기 I/O를 지원하기 위해서는 비동기 연산의 결과를 전달할 방법이 필요하다. 이를 위한 방법으로 노드에서는 1) 콜백 함수, 2) 이벤트 전송자, 3) 프라미스 등의 방식을 지원한다.


1) 콜백 함수

콜백 함수는 I/O 를 발생 시킬때 I/O가 종료되었을때 결과를 받을 익명 함수를 지정하는 방식을 사용한다. 예를 들어 다음과 같이 파일 시스템을 접근하는 함수 fs 모듈은 함수 호출시 콜백을 받아서 결과를 처리한다. 물론 fs는 동기식 파일 처리를 위한 함수들도 제공한다. (var data=fs.readFileSync('callback_ex.js'.'utf8') )

var fs=require('fs');

fs.readFile('callback_ex.js','utf8',function(error,data){

        if(error){

                return console.error(error);

        }

        console.log(data);

}); 

 콜백 함수는 연속해서 비동기 함수를 사용해야 할 경우에는 콜백 내에 콜백을 정의하는 "콜백 지옥"과 같은 가독성이 떨어지는 상황이 발생할 수 있다.


2) 이벤트 전송자

 이벤트 전송자 방법은 이벤트에 대한 핸들러를 등록하고, 이벤트를 발생시켜서 핸들러가 비동기적으로 I/O를 처리하도록 하는 것이다. 이벤트 전송자는 EventEmitter 데이터타입을 사용해서 생성한다. emitter 객체를 생성하고 on 메소드에 해당 이벤트를 리스닝하는 핸들러를 등록할 수 있다.emitter로 이벤트를 발생시킬 수 있다.

var events=require('events');

var EventEmitter=events.EventEmitter;

var emitter=new EventEmitter();

var counter=0;

emitter.on('start',function(){

        console.log('start');

});

emitter.on('increase',function(){

        counter++;

        console.log(counter);

});

emitter.emit('start');

emitter.emit('increase');

emitter.emit('increase'); 

$node eventemitter.js 

start

1

 on은 addListener 메소드와 같다. once는 이벤트는 일회성으로 이벤트를 처리한다. once는 이벤트가 발생하고 핸들러가 호출되고 난 다음에 이벤트 리스너는 제거된다.

 예외 처리는 'error'이벤트를 발생시키는 것이다. error 이벤트가 처리되지 않는다면 프로그램이 비정상 종료 될수 있다.

var EventEmitter=require('events').EventEmitter;

var emitter=new EventEmitter();

emitter.emit('error',new Error('Error Occured')); 

[실행시 다음의 에러가 발생한다.] 

events.js:141

      throw er; // Unhandled 'error' event

      ^

Error: Error Occured

 error 이벤트도 on 메소드를 이용해서 핸들러를 등록 가능하다. (emitter.on('error',function(error){...... )

 노드에서는 현재 동작 중인 프로세스와 상호 작용을 위해서 전역 process 객체를 제공한다. error 를 이벤트 리스너가 처리 하지 않으면 process 객체가 uncaughtException을 발생시킨다. process.on('uncaughtException',....) 으로 이 이벤트를 리스닝하고 처리할 수 있다.

var EventEmitter=require('events').EventEmitter;

var emitter=new EventEmitter();

process.on('uncaughtException',function(error){

        console.error(error.message);

        process.exit(-1);

});

emitter.emit('error',new Error('Error..')); 

 [실행하면 다음과 같이 출력된다.]

$ node eventprocess.js 

Error..

$


3) 프라미스

 프라미스는 비동기식 함수와 관련된 계약으로 생각할 수 있다. 비동기식 함수는 즉시 반환하지만, 미래의 특정 시점에 어떤 값이 존재할 것임을 약속하는 것이다. 프라미스를 처음 생성하면 미결(pending) 또는 미이행(unfulfilled)상태가 되며 관련된 비동기식 코드가 수행이 완료될 떄까지 이 상태로 남게 된다. 비동기식 코드가 성공적으로 완료되면 이행 상태(fulfilled) 상태로 변하고, 비동기식 호출이 실패하면 거절(rejected) 상태로 변한다. 앞의 콜백 예제는 다음과 같이 프라미스로 바꿀 수 있다.

var fs=require('fs');

var promise=new Promise(function(resolve,reject){

        fs.readFile('promise_ex.js','utf8', function(error,data){

                if(error){

                        return reject(error); //비동기 호출의 실패 전달

                }

                resolve('Success:'+data); //비동기 호출의 성공 전달

        });

});

promise.then(

        function(result){   // resolve 에 대한 이벤트를 처리한다.

                console.log(result);

        },

        function(error){ // reject에 대한 이벤트를 처리한다. 

                console.error(error.message);

        }

); 

 프라미스 연쇄는 then을 연쇄적으로 이용 가능하다. 즉 앞의 then에서 리턴하는 결과를 그 다음 then에서 처리 가능하다. 프라미스 연쇄를 이용할 경우 reject에 대해서는 catch 메소드를 이용한다.

var fs=require('fs');

var promise=new Promise(function(resolve,reject){

        fs.readFile('promise_ex.js','utf8',function(error,data){

                if(error){

                        return reject(error);

                }

                resolve('Success:'+data);

        });

});

promise.then(

        function(result){                              //resolve 에 대한 이벤트 처리

                return result;

        }).then(function(result){                  //앞의 then 이후 실행

                console.log(result);

                console.log('Success End');

        }).catch(function(error){                  //reject에 대한 이벤트 처리

                console.log(error.message);

        }).then(function(){                           //마지막에 항상 실행

                console.log('Final Msg'); 

        });


반응형
Posted by alias
,