Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How require() works when requiring the same module in node.js

When a module is required in node.js more than once it gives back the same object because require() cache the previous calls.

Lets say I have a main logger module what could register the sub logger modules. (Those make the logging actually through the main logger module log() function. But its not related here.)

I have something like this in the main logger module to add a sub-module:

module.addRedisLogger = function(rclient) {
     modulesArray.push(require('./redis.js')(rclient, loggingEnabled, module));
}

When I create a redis client instance, I could immediately add a logger to it like this:

var sub = redis.createClient();
logger.addRedisLogger(sub);

In the sub module I start to log like this:

module.startLogging = function() {
    rclient.on("message", messageCallback);
    rclient.on("pmessage", pmessageCallback);
}

And stop logging like this:

module.stopLogging = function() {
    rclient.removeListener("message", messageCallback);
    rclient.removeListener("pmessage", pmessageCallback);

}

But as far as I understand, with this technique I could only assign one redis logger instance because assigning the second one would return in the require() with the same object as in the first, so passing the new redis parameter would override the previous value. Because of that it would not be possible to stop the logging in the first redis logger instance because calling it, would stop the logging in the second instance.

Lets see an example:

var sub1 = redis.createClient();
var sub2 = redis.createClient();

sub1.subscribe("joinScreen1");
sub2.subscribe("joinScreen2");

logger.addRedisLogger(sub1);
logger.addRedisLogger(sub2);

// running redis-cli PUBLISH joinScreen1 message1
// running redis-cli PUBLISH joinScreen2 message2

logger.log("Lets stop the first logger);
logger.modulesArray[0].stopLogging()

// running redis-cli PUBLISH joinScreen1 message1
// running redis-cli PUBLISH joinScreen2 message2

I except to get this output:

// Message received on channel joinScreen1: message1
// Message received on channel joinScreen2: message2
// Message received on channel joinScreen1: message1

We should get this because the first logger now points to the second instance. So the redis points to the second client too.

But instead I get this:

// Message received on channel joinScreen1: message1
// Message received on channel joinScreen2: message2
// Message received on channel joinScreen2: message2

So it works as expected by PROGRAM DESIGN but not as expected by CODE. So I want to make it works as it works now, but I don't understand why it works like that.

UPDATE:

Long Version

module.js

var util = require("util");

module.exports = function () {
var module = {};

    module.show = function() {
        console.log(util.client);
    }

    module.set = function(value) {
        util.client= value;
    }

    return module;
};

main.js

var util = require("util");
util.client = "waaaa";

 var obj = require('./module')();

 obj.show();
 obj.set("weeee");

console.log(util.client);

Running the main.js will result this output:

C:\Users\me\Desktop>node main.js
waaaa
weeee

So require() gives back the very same object as the first time. If I changed it since then, then the changes are there too, because it is the same object.

Now lets say the variable redis is the same as client here and hold a reference to a redis connection. When the constructor runs the second time it overrides the first one, that is why I except to get the notifications from the logger of the first redis client, because there is no reference pointing at it, so the listener couldn't be removed.

like image 575
NoNameProvided Avatar asked Sep 15 '15 14:09

NoNameProvided


2 Answers

Like you said, require caches the RETURNED object of the require call. Take a look in your use case, where require is simply returning a function object, that's all. This function, when called, mutates some reference in memory, hence it works as expected. Caching a function, is not the same as caching the output of that function's call. In addition, returning a constructor function is the idiomatic way in node to expose classes, as every time you need a new object you can simply call the function. In other words, what you're doing is perfectly fine.

ex.

Case A

module.exports = new Date();

Case B

module.exports = function() {
  return new Date();
}

In case A, require will cache the date object, and will always return a reference to the same one. In case B, require will cache the function object, and will always return a reference to the same one. The difference is that in B, when calling that cached function, the function will return a new Date object each time.

like image 52
Yuri Zarubin Avatar answered Oct 22 '22 09:10

Yuri Zarubin


when you say this:

But as far as I understand, with this technique I could only assign one redis logger instance because assigning the second one would return in the require() with the same object as in the first, so passing the new redis parameter would override the previous value.

that is not what is happening in your case. Your module is exporting a single function:

module.exports = function (rclient, ploggingEnabled, logger) {

Calling require('./redis.js') more than once will return this function each time. That's true. And if you modify properties of that function, then require() it again, you will indeed see the modifications you made:

(require('./redis.js')).x = "waaa";
console.log((require('./redis.js')).x); // "waaa"

But when you actually invoke the function instead of modifying its properties, something different happens: a closure is created by declaring the startLogging() and stopLogging() functions, each of which manipulate variables higher up in the scope chain. This means that every time you call this exported function, a new closure is created with its own private pointers to rclient and ploggingEnabled and logger. If you save a reference to the value returned by this function (which you do, by push()ing it onto modulesArray) then you should be able to access each of those closures at a later time and perform whatever cleanup you need:

modulesArray.forEach(function(el, idx, arr) {
  el.stopLogging();
});
like image 30
Dan O Avatar answered Oct 22 '22 09:10

Dan O