Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript classes with getter and setter cause RangeError: Maximum call stack size exceeded

I am currently experimenting with ECMA6 classes. My current class looks like the following

class Player {
  constructor(id) {
    this.id = id;
    this.cash = 350;
  }

  get cash() {
    return this.cash;
  }

  set cash(value) { // line 19
    this.cash = value; // line 20
  }
};

When I am now creating a new Object by calling let playerObject = new Player(1); I receive the following error

...\node_modules\mysql\lib\protocol\Parser.js:82
        throw err;
              ^
RangeError: Maximum call stack size exceeded
    at Player.cash (player.js:19:11)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
Press enter to exit

What does this have to do with the mysql library? Why does the error is multiple times in the same line? I am only calling it once.

like image 810
Jannis Lehmann Avatar asked Jul 17 '15 20:07

Jannis Lehmann


People also ask

How do I fix error RangeError maximum call stack size exceeded?

The call stack is limited in size, and when it's exceeded, the RangeError is thrown. This can happen when a deeply nested function is called, or when a lot of new variables are created. The most common way to fix this error is to reduce the number of function calls, or to limit the number of variables that are created.

What is the maximum call stack size in JavaScript?

The consequences of applying a function with too many arguments (think more than tens of thousands of arguments) vary across engines (JavaScriptCore has hard-coded argument limit of 65536), because the limit (indeed even the nature of any excessively-large-stack behavior) is unspecified.

What does error RangeError maximum call stack size exceeded see JavaScript console for details mean?

The "RangeError: Maximum call stack size exceeded" error occurs when a function is called so many times that the invocations exceed the call stack limit. To solve the error, specify a base case that has to be met to exit the recursion.

Which of the given code snippet leads to maximum call stack size exceeded error?

Introduction. If you see the “Maximum Call Stack Size Exceeded” error, there's likely a problem with a recursive function within your JavaScript code. More specifically, the issue lies with the function calling on itself indefinitely.


4 Answers

Your "cash" setter calls the "cash" setter, which calls the "cash" setter, which calls the "cash" setter...

Accessing the property setter by its own name inside the setter creates an infinite recursive function call.

like image 154
Greg Burghardt Avatar answered Oct 13 '22 01:10

Greg Burghardt


I know I'm late, but I think I can clarify one or two points here:

First, there is the question of privacy, which is a long term discussion in the JavaScript community.

class Player {
   constructor(id) {
      this.cash = 350; // this._cash, alternatively
   }

   get cash() {
      return this.cash;
   }

   set cash(value) {
      this.cash = value;
   }
};

let player1 = new Player();

In this case, this.cash is a public property, so you don't really need a getter and a setter method to handle it, because you can get it with player1.cash and set it with player1.cash = newCash; and it is throwing the error because the getter and the setter are being called recursively, as mentioned by others.

However, if you simply rename the property to this._cash, you have to understand that this IS NOT A PRIVATE PROPERTY. If you try to access player1._cash, you will have access to the property value in the same way you will have with player1.cash.

So, how do we get privacy poperly implemented?

There are 2 main ways of doing this with ES6/ES2015: Using the new primitive type Symbol or using WeakMaps. I'm not going into details about this two new features of the language, but I'll show how this would be implemented in this case.

Using Symbols:

const CASH = Symbol();

class Player {

   constructor () {
      this[CASH] = 350;
   }

   get cash(){
      return this[CASH];
   }

   set cash(cash) {
      this[CASH] = cash;
   }

}

Using WeakMaps

let map =  new WeakMap();

class Player {

   constructor () {
      map.set(this, {
         cash: 350
      });    
   }

   get cash(){
      return map.get(this).cash;
   }

   set cash(cash) {
      map.get(this).cash = cash;
   }

}

IMPORTANT

While the syntax for Symbols are better, it requires native support of the browser to actually work. You can write it with a transpiler but, under the hood, it will mock it to old ES5 standards. The native support for WeakMaps are better and, on the other hand, this feature just plays with the GC and with the enumerable option of the objects properties. So, in the end, it's your choice.

like image 24
Matheus Dal'Pizzol Avatar answered Oct 13 '22 03:10

Matheus Dal'Pizzol


cash represents the getter/setter, _cash is the 'private' property.

  set cash(value) { // line 19
      this._cash = value; // line 20
  }

Have a look at this page for a clear example.

like image 20
htatche Avatar answered Oct 13 '22 01:10

htatche


You are calling recursively your getter.

It follows a possible alternative:

class Player {
    constructor(id) {
        this.id = id;
        this._cash = 350;
    }

    get cash() {
        return this._cash;
    }

    set cash(value) {
        this._cash = value;
    }
};

Another one using Object.defineProperty:

class Player {
    constructor(id) {
        this.id = id;

        var _cash = 350;
        Object.defineProperty(this, 'cash', {
            get: function() {
                return _cash;
            }

            set: function(v) {
                _cash = v;
            }
        });
    }
};
like image 32
skypjack Avatar answered Oct 13 '22 03:10

skypjack