2

I'm getting a RangeError: Maximum call stack size exceeded

function getUser(userId) {
    return new Promise((resolve, reject) => {
      controller.storage.users.get(userId, function(err, user) {
        if (err) reject(err);

        if (user) {
          if(!(user.orderData.pendingItem)) {
            getUser(userId)
          }
          else {
            resolve(user.orderData.pendingItem);
          }
        }
      })
    })
  };

The dilemma I'm having is that whenever I run controller.storage.users.get it does not always resolve all of the properties and values of the user object right away, which is why I attempt to run it again if orderData.pendingItem is not there.

However, I guess because it gets run so many times it's giving me a call stack error.

What's the best way to approach this or resolve this issue?

mralanlee
  • 479
  • 5
  • 15
  • 1
    This looks like a phone being used as a hammer. Surely the better solution would be to wait for whatever is setting `user.orderData.pendingItem` to finish instead. At the least, add some kind of delay to this. – Kevin B Sep 20 '17 at 21:12
  • Even if you get this to work, you have essentially made a `while(true)` but recursive. It would be better to make `pendingItem` a promise wherever its being populated, then in your code you can resolve it. That way your not creating a call stack while its waiting to be complete. – ug_ Sep 20 '17 at 21:19
  • @ug_ I agree with your comment. However my problem at the moment is that all the data is being stored in memory, while `user` or `orderData` should actually be stored in a DB so that I can place a promise on the query instead. However, I'm pretty much stuck untilI get the approval to move forward with a DB. – mralanlee Sep 20 '17 at 23:00

1 Answers1

4

Ideally, you should be listening for some event rather than polling, but on the understanding that this is a temporary workaround ...

new Promise() runs its constructor synchronously and it would appear that controller.storage.users.get() also runs its callback synchronously. The possibility of "Maximum call stack size exceeded" can be avoided by forcing the recursive getUser() call to be made asynchronously and the simplest way to do that is to chain new Promise().then(/* recurse from here */).

function getUser(userId) {
    return new Promise((resolve, reject) => {
        controller.storage.users.get(userId, function(err, user) {
            if (err) reject(err);
            if (!user) reject(new Error('no user for id: ' + useId)); // this branch needs to be catered for
            resolve(user); // make `user` available to the .then() below
        });
    }).then(function(user) {
        return (user.orderData && user.orderData.pendingItem) || getUser(userId); // compact and safe
    });
}

That should do the job but without the "Maximum call stack size" safeguard, stands a good chance of frying a processor or two for no good reason.

As suggested in the comments above, you can and probably should add some delay into the recursion :

function delay(t) {
    return new Promise(function(resolve) {
        setTimeout(resolve, t);
    });
}

function getUser(userId) {
    return new Promise((resolve, reject) => {
        controller.storage.users.get(userId, function(err, user) {
            if (err) reject(err);
            if (!user) reject(new Error('no user for id: ' + useId)); // this branch needs to be catered for
            resolve(user); // make `user` available to the .then() below
        });
    }).then(function(user) {
        return (user.orderData && user.orderData.pendingItem) || delay(200).then(function() { // adjust the delay to maximum acceptable value
            return getUser(userId);
        });
    });
}
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44