VertigoRay VertigoRay - 1 year ago 96
Node.js Question

TypeError: this[(("_" + (intermediate value)) + "_listener")] is not a function

I'm dynamically building an object that will have a sender on it:

var coins = {};

['USD','EUR'].forEach((product_id) => {
coins[`_${product_id}`] = {};
coins[`_${product_id}_listener`] = (val) => {
log.info(process.pid, 'terminal send', product_id, [`_${product_id}`]);
terminal.send({
action: product_id,
data: this[`_${product_id}`],
timestamp: new Date,
});
};

Object.defineProperty(coins, product_id, {
set: (val) => {
this[`_${product_id}`] = val;
this[`_${product_id}_listener`](val);
},
get: (val) => {
return this[`_${product_id}`];
},
});
});


Unfortunately, when I set a coin to something ...

coins['USD'] = {a: 4};


I get an error:

TypeError: this[(("_" + (intermediate value)) + "_listener")] is not a function
at Object.set (repl:15:32)
at repl:1:18
at sigintHandlersWrap (vm.js:22:35)
at sigintHandlersWrap (vm.js:73:12)
at ContextifyScript.Script.runInThisContext (vm.js:21:12)
at REPLServer.defaultEval (repl.js:340:29)
at bound (domain.js:280:14)
at REPLServer.runBound [as eval] (domain.js:293:12)
at REPLServer.<anonymous> (repl.js:538:10)
at emitOne (events.js:101:20)


This is obviously referring to the second line of the setter, but I'm not sure why. If I comment that out, gets and sets work fine. My listener just doesn't fire ... obviously. The goal here is to get the listener to fire.

Follow up



I've also noticed, with that line commented out, that the object doesn't look right (imo):

> coins['USD'] = {a: 4};
{ a: 4 }
> coins
{ _USD: {},
_USD_listener: [Function],
_EUR: {},
_EUR_listener: [Function] }
> coins['_USD']
{}
> coins['USD']
{ a: 4 }


Since my setter is supposively setting
_USD
, why doesn't
_USD
look set when I print out
coins
?

Answer Source

The problem is that arrow functions unlike normal function does not allow to change context (this). Docs.

An arrow function expression has a shorter syntax than a function expression and does not bind its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions

So when you create an arrow function context will be captured once an for all calls.

You could iether switch to normal functions or create arrow functions using coins as a context. For example by passing coins as a context to .forEach call and replacing iteratee with a normal function to allow dynamic context.

var coins = {};

['USD','EUR'].forEach(function(product_id) {
    this[`_${product_id}`] = {};
    this[`_${product_id}_listener`] = (val) => {
        console.log(`${product_id} - ${val}`)
    };

    Object.defineProperty(coins, product_id, {
        set: (val) => {
            this[`_${product_id}`] = val;
            this[`_${product_id}_listener`](val);
        },
        get: (val) => {
            return this[`_${product_id}`];
        },
    });
}, coins)

coins.USD = 10
coins.EUR = 100
console.log(`USD ${coins.USD}, EUR ${coins.EUR}`)

Or better use normal function because you do have "methods"

    var coins = {};

    ['USD','EUR'].forEach(product_id => {
        coins[`_${product_id}`] = {};
        coins[`_${product_id}_listener`] = function(val) {
            console.log(`${product_id} - ${val}`)
        };

        Object.defineProperty(coins, product_id, {
            set: function(val) {
                this[`_${product_id}`] = val;
                this[`_${product_id}_listener`](val);
            },
            get: function() {
                return this[`_${product_id}`];
            },
        });
    })

    coins.USD = 10
    coins.EUR = 100
    console.log(`USD ${coins.USD}, EUR ${coins.EUR}`)

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download