1

I was writing some code for a widget in "Scriptable" that shows random word at a certain time. The widget calls itself with a timeout, on iPad. But it's exceeding the stack size. How do I solve this? I am using Javascript within "Scriptable" framework, so I don't have much freedom.

kanjiObj: Object; kanjiKeys: List; Timer().schedule(timeinterval, repeat, callback)

var randomNum = Math.floor(Math.random()*150)+1
var kanji = kanjiKeys[randomNum]
var kanjiMeaning = kanjiObj[kanjiKeys[randomNum]]

if(config.runsInWidget){
  let widget = createWidget()
  Script.setWidget(widget)
  Script.complete()
  
function createWidget(){
  let widget = new ListWidget()
  widget.addText(kanji + "=>" + kanjiMeaning)
  widget.wait = new Timer().schedule(1000*60*60, 150, createWidget())
Crack Head
  • 11
  • 1
  • It's probably supposed to be `widget.wait = new Timer().schedule(1000*60*60, 150, createWidget)` (just passing the function itself, not the undefined return value while creating the infinite loop) –  Jan 09 '22 at 09:48

1 Answers1

0

In your case, you are calling the createWidget() function recursively by mistake. Your intention was different, you wanted to pass the function as a parameter to the schedule function, which accepts a function callback as the last parameter.

So you have to change to

widget.wait = new Timer().schedule(1000*60*60, 150, () => createWidget())

or

widget.wait = new Timer().schedule(1000*60*60, 150, createWidget)

But since this question is about "call stack size exceeded" people who have a different kind of this problem could stumble upon it, so I will answer in a general way below as well.


Programming is about recognizing patterns, so what you have in your code is a pattern (structure) of an infinite recursive call

function someFunction() {
    // ... code before the recursive call that never returns from this function
    
    // an unconditional call (or a conditional chain that is always true)
    // of the function itself in the function scope instead of inside a callback/lambda
    someFunction()

    // ... could be more code, which does not matter,
    // until the pattern above still holds true
}

So a structure (pattern) like this will result in an infinite recursive call, which eventually ends with a maximum call stack size exceeded. Call stack size is limited and an infinite recursive call eventually fills it up.

Javascript does not have built-in tail-recursion optimization (in practice, see compatibility), but in programming languages that do, if the function is tail-recursive, it would be optimized to a loop. In the case of an infinite tail-recursive function it would be optimized to an infinite loop, which would not fill the call stack, it would just loop forever (it would be working, but if not implemented correctly, it could make the application unresponsive).

So to resolve a problem like this in general, one has to break this pattern by breaking any (at least one) of the conditions above. This is the list:

  • code before the recursive call that never returns from this function

    • add a conditional return statement (also called a stop condition)
  • an unconditional call (or a conditional chain that is always true) to the function itself in the function scope instead of inside a callback/lambda

    • make the call conditional (make sure the condition chain can be false) or put it inside a callback/lambda. When you put it inside a callback/lambda, then a different pattern applies (you have to check the logic inside the call that will be calling the callback), but calling the callback still has to be conditional, it has to have a limit at some point.
  • after making a change, the code that is after the call, needs to be checked for the same pattern again, if the pattern is there again, break it in a similar way. Keep repeating this until the whole function does not form the pattern anymore - has stop condition(s) where needed.

In cases when you do need an infinite recursive call and the function is tail-recursive and your language does not support tail-recursion optimization, you would need to do the tail optimization yourself or use a library/framework that lets you do that.

If this does not solve your problem, have a look at the answers in this question that has collected a lot of reasons why a "maximum call stack size exceeded" might happen in Javascript.

Ma3x
  • 5,761
  • 2
  • 17
  • 22