Uzair
Uzair

Reputation: 643

Why does wrapping a function in javascript cause recursion and stack overflow error?

I'm trying to wrap a list of functions over a callback function. Each of the function in the list takes in the value of the callback and returns a modified value. When I try to do this in a straight forward way, it does recursion, and the stack eventually runs out of space, and throws an error.

I tried solving the problem by using a wrapper function that took in a function, wrapped it with another one, and then returned it, and that solved the problem.

Look at the subscribe function:

class Observable {
    constructor() {
        this._subscribers = [];
        this._operators = [];
    }
    next(val) {
        this._subscribers.forEach(subscriber => {
            subscriber(val);
        });
    }
    subscribe(callback) {
        if (this._operators.length > 0) {
            let ogCallback;
            this._operators.forEach((operator, index) => {
                ogCallback = callback;
                /** ==== call stack full error =====
                 * callback = (val) => {              
                 *    ogCallback(operator(val));    
                 * };
                 */

                // This works
                callback = ((func) => {
                    const wrapper = (val) => {
                        func(operator(val));
                    };
                    return wrapper;
                })(ogCallback);
            });
            this._operators = [];
        }
        this._subscribers.push(callback);
    }
    pipe(operator) {
        this._operators.unshift(operator);
        return this;
    }
}

const observable = new Observable();
observable.pipe(val => val + 2).pipe(val => val * 2).subscribe(val => console.log(val));

observable.next(5);

Why does this happen? They both seem to be the same thing.

Upvotes: 0

Views: 69

Answers (1)

RobG
RobG

Reputation: 147413

I suspect it's from the series of closures created by:

ogCallback = callback;
callback = (val) => {
   ogCallback(_function(val));
}

ogCallback and callback are global. After the initial iteration, callback has the value:

(val) => ogCallback(_function(val))

ogCallback has a closure to the global ogCallback, so it's value is whatever it was given from the last iteration, as does callback, potentially causing circular references.

The second example breaks the closure by creating a local variable func in the assigned function expression that is passed the value of ogCallback using an immediately invoked function expression (IIFE).

Original second example

_functions.forEach((_function, index) => {
    ogCallback = callback;
    callback = ((func) => {
        const wrapper = (val) => {
            func(_function(val));
        };
        return wrapper;
    })(ogCallback);
});

Upvotes: 1

Related Questions