2014년 5월 20일 화요일

node cycle 관련

노드에서 global 영역에 require 를 사용하면 해당 모듈을 init 하는대 필요한 모듈이라고
가정하기 때문에 cycling 을 발생한다.

그렇기 때문에 메서드 안에서 runtime 시 디펜던시가 호출되게 하는 방법이 있다.
(require 는 한번 호출시 캐슁하기때문에 성능 문제는 없다)

1.메서드 안에서 require 사용
//a.js
function a(){

}

a.prototype.onlyAFunction = function(){
  return 'a';
}

a.prototype.withBFunction = function(){
  return require('./b').onlyBFunction();
}

module.exports = new a();

//b.js

function b(){

}

b.prototype.onlyBFunction = function(){
  return 'b';
}

b.prototype.withAFunction = function(){
  return require('./a').onlyAFunction();
}

module.exports  = new b();

//main.js
var a = require('./a');
var b = require('./b');


console.log(a.onlyAFunction());
console.log(a.withBFunction());

console.log(b.onlyBFunction());
console.log(b.withAFunction());

결과
a
b
b
a

2.한쪽에만 의존성이 있을경우 dependency injecttion 을 사용할수 있으나
코드간 의존성이 생기기때문에 개인적으로 비추다..
//a.js
var b = require('./b');

function a(){

}

a.prototype.onlyAFunction = function(){
  return 'a';
}

a.prototype.withBFunction = function(){
  return new b(this).onlyBFunction();
}

module.exports = new a();
//b.js
function b(a){
  this.a = a;
}

b.prototype.onlyBFunction = function(){
  return 'b';
}

module.exports = b;

//main.js
var a = require('./a');
var b = require('./b')(a);


console.log(a.onlyAFunction());
console.log(a.withBFunction());

console.log(b.onlyBFunction());
console.log(b.withAFunction());
3.객체를 생성하지 않고 exports의 property로 사용하는 경우
아래의 경우 객체 생성시 사용되는 의존성이 아니기때문에
(userService, planService 등과 같이 서비스로 사용할시)
가장 깔금하나 모듈 생성시 function(생성자) 를 사용해야 할 경우 문제가 발생함으로 공통코딩 규칙으로 잡기 머하다.


//a.js
console.log('a starting');
exports.done = false;

var b = require('./b4');

exports.onlyAFunction = function(){
  return 'a';
}

exports.withBFunction = function(){
  return b.onlyBFunction();
}

console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');

//b.js

console.log('b starting');
exports.done = false;

var a = require('./a4');

exports.onlyBFunction = function(){
  return 'b';
}

exports.withAFunction = function(){
  return a.onlyAFunction();
}
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');

//main.js
console.log('main starting');
var a = require('./a4');
var b = require('./b4');


console.log(a.onlyAFunction());
console.log(a.withBFunction());

console.log(b.onlyBFunction());
console.log(b.withAFunction());

console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

결과
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
a
b
b
a
in main, a.done=true, b.done=true

5.위에서 언급한 생성자 사용시 발생하는 문제다.
아래 문제는 노드에서 require 싸이클이 발생할 경우
중간에 완성이 들된 object 를 리턴하기 때문에 발생한다.
참조 http://nodejs.org/api/modules.html#modules_cycles



//a.js
console.log('a starting');
exports.done = false;

var b = require('./b5');

function a(){

}

a.prototype.onlyAFunction = function(){
  return 'a';
}

a.prototype.withBFunction = function(){
  return b.onlyBFunction();
}

module.exports = new a();
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');

//b.js
console.log('b starting');
exports.done = false;

var a = require('./a5');

function b(){

}

b.prototype.onlyBFunction = function(){
  return 'b';
}

b.prototype.withAFunction = function(){
  return a.onlyAFunction();
}


module.exports = new b();
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');

//main.js
console.log('main starting');

var a =  require('./a5');
var b =  require('./b5');

console.log(1);
console.log(a.onlyAFunction());
console.log(2);
console.log(a.withBFunction());
console.log(3);
console.log(b.onlyBFunction());
console.log(4);
console.log(b.withAFunction());
console.log('end');
2
결과
a starting
b starting
in b, a.done = false
b done
in a, b.done = undefined
a done
1
a
2
b
3
b
4

/Users/yeonghuni/workspace/wishbeen/test/test5/b5.js:19
  return a.onlyAFunction();
           ^
TypeError: Object #<Object> has no method 'onlyAFunction'
    at b.withAFunction (/Users/yeonghuni/workspace/wishbeen/test/test5/b5.js:19:12)

4번후 b.withAFunction()을 호출할때 에러가 발생한다.
왜냐하면 최초 a 생성시  b가 필요해서 b를 생성한다.
이때 b에서 다시 a 를 호출하는대 싸이클링이 생성되기 때문에
덜만들어진 a의 객체를 리턴한다. (이때 a.onlyAFunction)이 존재하지 않는다.
그러므로 실재로 코드를 호출하게 되면 런타임시 에러가 발생한다.

이때 정의된 a 가 왜? 런타임에서 에러가 발생하는가?
자바스크립트에서 idetifier resoultion 은 scope chain 을 대상으로 이루어진다.

각각의 함수는 실행시 자신의 execution context 에서 실행되게 되는대 이때 execution context는 activation/variable object + 부모[[scope(chain)]]이다.

(scope chain 은 function 이 정의 될때 자신이 호출되는 function 의 scope 체인(리스트) 속성  맨앞에 activavion/variable object 를 추가하여 만들어진다.)

위의 경우 new b() 를 하게 되면 b의  activavion/variable object  의 a는 생성되다만 object 이다.

b.withAFucntion 을 할경우 b를 호출 한다 이때 b에는 함수가 없음으로 prototype을 뒤지는대 이때 b.prototyp.withAFunction 이 호출된다.

이때 b.prototyp.withAFunction 은 b가 require 될때 생성되었기 때문에 scope 의 a 객체는
정의 되다 만 객체이다.

정의되다만 객체에는 a의 메서드가 없기 때문에 에러가 발생한다.

https://github.com/joyent/node/blob/master/src/node.js#L738

물런 a가 레퍼런스로 넘어갔으니 b가 완성되고 a 가 완성된 후
코드가 호출 되니 a에 메서드가 호출되야 하지 않느냐 라고 물어볼수 있다.

activation/variable object 는 해당 object 의 현재 모습을 copy 해서 저장하기 때문이다.

즉 main에서 b.withAFunction 은 함수 생성 순간에 복사된 a 오브젝트를 지속적으로 참조한다.