Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript Decorator pattern. Error: Maximum call stack size exceeded

Here is a working example of Decorator pattern:

class Dummy {
    run() {
        console.log('run');
    }
}

function get() {
    let instance = new Dummy();
    instance.run = ((func) => {
        return function() {
            func();
            console.log('decorator run');    
        }
    })(instance.run);

    return instance;
}

let obj = get();
obj.run();

However, if we change the get function to:

function get() {
    let instance = new Dummy();
    instance.run = function() {
        instance.run();
        console.log('decorator run');  
    }       

    return instance;
}

we will be faced with an error: VM68418:6 Uncaught RangeError: Maximum call stack size exceeded at Dummy.instance.run (:6:32)

Why is this happening? The instance.run is still a wrapper around the original method, without 'useless' additional self executed function.

I will be glad to hear the answer

like image 630
Y Bond Avatar asked Jan 26 '18 09:01

Y Bond


People also ask

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

The most common source for this error is infinite recursion. You must have a recursive function in your code whose base case is not being met and is, therefore, calling the function again and again until you hit the call stack limit.

How do I fix maximum call stack size exceeded see JavaScript console for details?

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.

What is the maximum call stack size 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 it mean when maximum call stack size exceeded?

The JavaScript exception "too much recursion" or "Maximum call stack size exceeded" occurs when there are too many function calls, or a function is missing a base case.


2 Answers

instance.run() is being called inside its own definitation, so it is causing a never ending recursion, hence causing a Maximum call stack size exceeded error.

like image 189
Waleed Iqbal Avatar answered Oct 13 '22 02:10

Waleed Iqbal


I believe it's dangerous to get tangled in made-up things like "decorators", "decorator pattern", or even "patterns". At the core of your issue, you have a function whose behaviour you wish to alter, or decorate...

const original = x =>
  x * x
  
const decorate = f =>
  x => f (x) + 1
  
const decorated =
  decorate (original)

console.log (original (4))  // 16   4 * 4
console.log (decorated (4)) // 17   (4 * 4) + 1

So with decorate, we're capturing this incrementing + 1 effect, but notice we were forced to decide when to increment; before or after the original function was called. Maybe in a different variation, we want to "decorate" using this +1 effect but at the opposite time.

Below, firstAdd1 is a "decorator" that increments before the original function is called. thenAdd1 is a decorator that increments after the original function is called.

const original = x =>
  x * x
  
const thenAdd1 = f =>
  x => f (x) + 1

const firstAdd1 = f =>
  x => f (x + 1)
  
const decoratedA =
  thenAdd1 (original)

const decoratedB =
  firstAdd1 (original)
  
console.log (original (4))   // 16   4 * 4
console.log (decoratedA (4)) // 17   (4 * 4) + 1
console.log (decoratedB (4)) // 25   (4 + 1) * (4 + 1)

But now we've sort of duplicated the +1 effect. "Decorating" as it turns out, is just function composition. Realizing this, we remove pain and suffering from our program.

Below, we capture our +1 effect in a pure function, add1, and then simply compose it before or after a given f

const add1 = x =>
  x + 1

const compose = (f, g) =>
  x => f (g (x))

const thenAdd1 = f =>
  compose (add1, f)

const firstAdd1 = f =>
  compose (f, add1)

No objects were harmed in the making of this program

const original = x =>
  x * x

const add1 = x =>
  x + 1
  
const compose = (f, g) =>
  x => f (g (x))

const thenAdd1 = f =>
  compose (add1, f)

const firstAdd1 = f =>
  compose (f, add1)
  
const decoratedA =
  thenAdd1 (original)

const decoratedB =
  firstAdd1 (original)
  
console.log (original (4))   // 16   4 * 4
console.log (decoratedA (4)) // 17   (4 * 4) + 1
console.log (decoratedB (4)) // 25   (4 + 1) * (4 + 1)

Of course function composition is massively powerful. We can modify compose to accept an arbitrary number of functions. Now we can sequence any number of effects in any order of our choosing. Here, we also skip the intermediate creation of "decorators" and instead define "decorated" functions directly in terms of compose

const original = x =>
  x * x

const add1 = x =>
  x + 1
  
const compose = (f, ...fs) => x =>
  f === undefined
    ? x
    : f (compose (...fs) (x))

const decoratedA =
  compose (add1, original, add1)
  
const decoratedB =
  compose (add1, add1, add1, original, original)

const decoratedC =
  compose (decoratedB, decoratedA)
  
console.log (original (4))   // 16       4 * 4
console.log (decoratedA (4)) // 26       ((4 + 1) * (4 + 1)) + 1
console.log (decoratedB (4)) // 259      ((4 * 4) * (4 * 4)) + 1 + 1 + 1
console.log (decoratedC (4)) // 456979   (((((4 + 1) * (4 + 1)) + 1) * (((4 + 1) * (4 + 1)) + 1)) * ((((4 + 1) * (4 + 1)) + 1) * (((4 + 1) * (4 + 1)) + 1))) + 1 + 1 + 1

Yep, because compose returns a new function, we can even make compositions of other compositions. Even compose side-effecting functions like console.log using effect which guarantees the output matches the input

Below logger allows us to visualize any particular function's impact by logging the result to the console before returning the final value – to this end, you could say logger (f) decorates f by adding a logging behaviour – but it's just classical function composition

const square = x =>
  x * x

const add1 = x =>
  x + 1
  
const compose = (f, ...fs) => x =>
  f === undefined
    ? x
    : f (compose (...fs) (x))
      
const effect = f => x =>
  (f (x), x)
  
const logger = f =>
  compose (effect (console.log), f)

const main =
  compose (logger (add1), logger (square))

console.log (main (4))
// 16     (console.log side effect)
// 17     (console.log side effect)
// => 17  (return value)

If you're writing OO-style with classes and methods, it doesn't matter; compose is still your go-to

const compose = (f, ...fs) => x =>
  f === undefined
    ? x
    : f (compose (...fs) (x))
      
const effect = f => x =>
  (f (x), x)

const addExcitement = x =>
  x + '!'
  
const capitalize = x =>
  x.toUpperCase ()

class Person {
  constructor (name) {
    this.name = name
  }
  greet () {
    return `I am ${this.name}`
  }
}

// "decorator"
const Shouter =
  effect (m => 
    m.greet = compose (addExcitement, capitalize, m.greet.bind(m)))

const p = new Person ('me')
console.log (p.greet ())    // I am me

Shouter (p)
console.log (p.greet ())    // I AM ME!
like image 24
Mulan Avatar answered Oct 13 '22 00:10

Mulan