Linux XXXXX 3.19.0-32-generic #37~14.04.1-Ubuntu SMP Thu Oct 22 09:41:40 UTC 2015 x86_64 x86_64 x86_64 GNU/LinuxAssume you have the following three JavaScript source files:
const T2 = require("./T2.js");
module.exports = class T1 {
log() { console.log("T1"); }
test(value) { return (value instanceof T2); }
};
const T1 = require("./T1.js");
module.exports = class T2 {
log() { console.log("T2"); }
test(value) { return (value instanceof T1); }
};
Note that both files 'require' each other. Think of those two classes as having a parent-child relationship and both having methods to establish the relationship by passing references to each other. The 'instanceof' operator would be useful here to 'assert' that the given references are what they are supposed to be; i.e. instances of the corresponding class.
const T1 = require("./T1.js");
const T2 = require("./T2.js");
function tryTest(instance, value) {
try {
console.log(instance.test(value));
} catch(error) {
console.log("ERROR");
}
}
function test_instanceof() {
let t1 = new T1();
let t2 = new T2();
t1.log();//- T1
t2.log();//- T2
console.log("t1.test()");
tryTest(t1, t1);//- false
tryTest(t1, t2);//- true
console.log("t2.test()");
tryTest(t2, t1);//- ERROR
tryTest(t2, t2);//- ERROR
};
test_instanceof();
If you run the code from inside a terminal/console, you will get the following output:
(the console will of course print one part per line -- just squashed into one line to keep it small)
node main.js
T1, T2, t1.test(), false, true, t2.test(), ERROR, ERROR
And if you switch the first two lines inside main.js to:
const T2 = require("./T2.js");
const T1 = require("./T1.js");
you will get:
node main.js
T1, T2, t1.test(), ERROR, ERROR, t2.test(), true, false
If you log the error variable inside 'catch(error)' to the console, it will print the following message each time the 'instanceof' operator fails:
TypeError: Right-hand side of 'instanceof' is not callable
If I set a breakpoint inside T1.js at the line containing the 'instanceof' operator and hover the mouse cursor over T2's class name "T2", while having the original require-order in main.js, I get to see a popup that displays T2's source code; the 'instanceof' operator works.
If I do the same inside T2.js, the popup that is shown when hovering above "T1" will display (Object) #<Object>; the 'instanceof' operator fails.
Umm, labeled as "question"?
I'd say it is a runtime bug if a working 'instanceof' operator will fail after merely changing the order of 'require' statements.
Umm, labeled as "question"?
Well… I understand why one might want to call it a bug, but really it’s not something we can do anything about beyond explaining why this happens.
The problem is that you have a dependency cycle, and each of the require calls has to return something immediately, since require works synchronously. So what happens is that you require T1.js, which requires T2.js before it is finished evaluating itself; the require('T1') call inside of T2.js “knows” that T2.js isn’t finished evaluating, so it gives you the exports object of it and hopes that that is enough for you to work with.
But that obviously doesn’t give you the right thing, because you later override the exports object of T1 – but by that time T2.js has already been evaluated and uses a reference to the wrong exports.
See this for more details: https://nodejs.org/api/modules.html#modules_cycles
(By the way, ES6 modules won’t have that problem, but we’re still a bit from having support from that – you’ll need to wait for Node 9, at least.)
I’m closing this as answered, but feel free to ask any follow-up questions.
Thanks, @addaleax for your quick response.
It seems like I needed your explanation to find a workaround for the problem: All that needs to be done to avoid the issue is to move the require statements below the class definitions. Once that is done, the 'instanceof' operator will work as intended.
This is a little bit frustrating, I found myself not being able to trust instanceof at all, I put some big named variable in the super class with value true and check for that. It's clumsy, its ugly, but it works.
Just came across this. :-(