0

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.

4
  • 2
    Neither of them does anything (neither callback is called), so I don't see how it could affect the stack size. Perhaps you could include a minimal reproducible example that shows how you are calling the functions (and the callback)? Commented Aug 25, 2019 at 23:53
  • 1
    You haven't declared ogCallback in the local scope, so you only have one ogCallback shared across all your callbacks in the first case. The second one they each have their own one called func. Commented Aug 25, 2019 at 23:55
  • What is the initial value of callback in both functions? On each iteration, it will initially have the value assigned on the previous iteration. You assign that to ogCallback, then call that previous value in the "wrapped" function (which is assigned but never called, so is essentially irrelevant). So each iteration gets the function from the previous iteration. Is that expected? It creates a chain of closures (which are never called/executed). Commented Aug 26, 2019 at 0:42
  • Sorry for not including the usage earlier. Did that now. Commented Aug 26, 2019 at 1:08

1 Answer 1

1

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);
});
Sign up to request clarification or add additional context in comments.

1 Comment

I can't say I completely understood the reason. Will need to contemplate some more. But changing ogCallback to a local variable, fixed the issue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.