
Watch out for globals in node.js modules!
globals, or global variables are known to be risky.
However using the ‘var’ keyword should ensure file level definition.
As such shouldn’t it be safe to use module level variables?
The answer is no, and it should be avoided at all costs.
why module level variables are bad?
Node require will wrap your module with a function as follows:
~ $ node
require('module').wrapper
[ '(function (exports, require, module, __filename, __dirname) { ',
'\n});' ]
The calling node will assign to these arguments when it will invoke the wrapper function.
This is what makes them look as if they are globals in the scope of your node module.
It seems we have globals in our module however:
– export is defined as a reference to module.exports prior to that.
– require and module, are defined by the function executed.
– __filename and __dirname are the filename and folder of your current module.
caching – a double edge sword
Node will then cache this module, so the next time you require the file, you won’t actually get a fresh copy, but you’ll be getting the same object as before.
This means you’ll be using the same global modules variables in multiple places, which means danger!
Here is a code example that illustrated the problem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//moduletest.js 'use strict'; var x = 0; module.exports = function (val) { console.log(`val : ${val}, x: ${x}`); if (val !== x && x !== 0) throw new Error(`failure!!! ${x} != ${val}`); x = val; } //main.js const fn1 = require('./moduletest'); const fn2 = require('./moduletest'); setInterval(function () { fn1('a'); },200 ); setInterval(function () { fn2('b'); },50 ); |
I’m running here two calls to the same function, with a small delay between each call, after a few runs we will notice that the function will run over each others variables. Which is an example of a module global issue.
How to solve globals?
There are multiple potential solutions to this global issue, I’ll present you with two potential solutions
Solution 1 – Functional
If we define a local scope inside our module, we can return a new set of variables for each run.
We will use a ‘let’ keyword, along with a scoped function (not needed, but nicer and better scope control).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//testmodule.js 'use strict'; module.exports = (function() { let x = 0; return function (val) { console.log(`val : ${val}, x: ${x}`); if (val !== x && x !== 0) throw new Error(`failure!!! ${x} != ${val}`); x = val; } }); //main.js fn1 = require('./testmodule')(); //<--- calling a function each time fn2 = require('./testmodule')(); // fn1 and fn2 are new functions with new variables, we busted the cache !! :) // notice I also use let, to ensure scope variables, and not hoisted vars. |
Solution 2 – use Classes
We can just define a class then create a new class for each run.
This way each variable is a private member of that class, ensuring proper encapsulation.
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 |
//testmoduleclass.js 'use strict'; class FunctionRunner { constructor() { this.x = 0; } fn(val) { console.log(`val : ${val}, x: ${this.x}`); if (val !== this.x && this.x !== 0) throw new Error(`failure!!! ${this.x} != ${val}`); this.x = val; } } module.export = FunctionRunner; //testclass.js const FunctionRunner = require('./testmoduleclass.js'); const fn1 = new FunctionRunner(); const fn2 = new FunctionRunner(); // now each fn holds it's own set of variables. // no risk at all :) |
For complete code have look at this repository:
https://github.com/CoreTeamIO/globals-in-node-modules