6

My goal is a slideshow of background images with HTML/CSS/JS. Many solutions that I've found promote something like this:

my_recursion();

function my_recursion () {
 // cycle the Background image ...
 setTimeout(my_recursion, 3000);
}

Am I wrong to assume that this is bad style? I would expect that at e.g. cycle 1000 all the other 999 instances of my_recursion are still open / on the stack? Doesn't this create and infinite stack which consumes more and more memory?

Or is there some sort of intelligence involved which does something like "if a function calls itself at the end, the (n-1)th function is destroyed including all variables that were assigned inside of it"?

10
  • 2
    There is only one entry of my_recursion on the stack ever. The first execution finishes completely before the second one is launched. Commented Jan 30, 2019 at 15:17
  • Probably not exactly a dupe, but I've written before about call stack, recursion, and setTimeout (as a mechanism to interact with the queue) before Commented Jan 30, 2019 at 15:18
  • But why? Does that mean a var x = 1 right after my setTimeout would never be executed? Commented Jan 30, 2019 at 15:20
  • 1
    @Robert the calls to setTimeout() return immediately. The system keeps track of the pending timer and calls the callback function when the time comes. Commented Jan 30, 2019 at 15:20
  • 2
    @Robert no, it's not possible. I urge you to have a look at the other question I linked and potentially look into the event queue more. If your function is still running in 3s then nothing else would be running. Only when it finishes, any other scheduled code would run - you won't get two parallel executions. Commented Jan 30, 2019 at 15:33

4 Answers 4

6

This will not result in endless stack increase, because of the way setTimeout works, and imho it is not bad style.

setTimeout does not guarantee that code will run directly after the given timeout. Instead, after that timeout it will push the callback onto a "queue", which will be processed when the stack is empty. So it will only run when my_recursion has returned and the stack is empty.

If a function calls itself at the end (...)

my_recursion doesn't call itself anywhere. It just passes itself as an argument to setTimeout. After that, it will just continue executing, return directly after, and will be popped from the stack.

This presentation explains the stack and the event queue.

Sign up to request clarification or add additional context in comments.

5 Comments

Does that mean, IF I had code right after setTimeout which took longer than 3 seconds to execute (e.g. 4 seconds), the next function would start to execute in parallel while my (n-1)th instance is still running? Or does that mean that the next instance starts after 4 seconds because it was unable to start after 3 since the (n-1)th function was still running?
I don't think there is a requirement for the function to call itself immediately and explicitly for it to be recursive. As long as it results in another execution of the same function, it should be recursive. If only direct and explicit calls were the requirement, then tail-call optimisation (TCO) would matter when considering if a function is recursive or not.
@Robert: No, the code would not execute in parallel. setTimeout does not guarantee that code will run directly after the given timeout. Instead, after that timeout it will push the callback onto a "queue", which will be processed when the stack is empty. So it will only run when my_recursion has returned and the stack is empty.
I've added a link to a presentation at JSConf that explains this.
@vlaz: You are right, I've updated my answer to more accurate.
2

In your question, your function does not have any parameters. In a real implementation, I hope you plan to use them.

const cycleBackground = (elem, bgs = [], ms = 1e3, i = 0) =>
  ( elem.setAttribute ('style', bgs[i])
  , setTimeout
      ( cycleBackground      // function to schedule
      , ms                   // when to schedule, ms from now
      , elem                 // user-specified element to change
      , bgs                  // user-specified backgrounds
      , ms                   // user-specified delay
      , (i + 1) % bgs.length // next background index
      )
  )

const backgrounds =
  [ "background-color: red;"
  , "background-image: linear-gradient(45deg, cyan 0%, purple 75%);"
  , "background-color: green;"
  ]

// call site
cycleBackground
  ( document.body // element to target
  , backgrounds   // list of backgrounds
  , 3e3           // delay, 3 seconds
  )
p {
  text-align: center;
  font-size: 3vw;
  font-weight: bold;
  color: white;
}
<p>Wait 3 seconds...</p>

Comments

0

The code is fine. It destroys all the variables because when you call it first time. It setTimeout() for the next function and at last return. You function doesnot return the the next.

my_recursion();

function my_recursion () {
 // cycle the Background image ...
 setTimeout(my_recursion, 3000); //Sets timeout for next function.
 //returns undefined here
} 

Comments

0

Adding to https://stackoverflow.com/a/54443904/11022136. Wanted to give some evidence. Ran the following on node 14.

test.js:

let i = 10;
const canThisOverflow = () => {
    i--;
    console.trace();
    if (i > 0) setTimeout(canThisOverflow, 1);
}
canThisOverflow();

Output: Stack size does not increase

Trace
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test.js:4:10)
    at Object.<anonymous> (/Users/arjunmalik/Shipsy/query-builder/test.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47
Trace
    at Timeout.canThisOverflow [as _onTimeout] (/Users/arjunmalik/Shipsy/query-builder/test.js:4:10)
    at listOnTimeout (internal/timers.js:554:17)
    at processTimers (internal/timers.js:497:7)
Trace
    at Timeout.canThisOverflow [as _onTimeout] (/Users/arjunmalik/Shipsy/query-builder/test.js:4:10)
    at listOnTimeout (internal/timers.js:554:17)
    at processTimers (internal/timers.js:497:7)

test2.js:

let i = 10;
const canThisOverflow = () => {
    i--;
    console.trace();
    if (i > 0) canThisOverflow();
}
canThisOverflow();

Output: Stack size increases

Trace
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:4:10)
    at Object.<anonymous> (/Users/arjunmalik/Shipsy/query-builder/test2.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47
Trace
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:4:10)
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:5:13)
    at Object.<anonymous> (/Users/arjunmalik/Shipsy/query-builder/test2.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47
Trace
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:4:10)
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:5:13)
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:5:13)
    at Object.<anonymous> (/Users/arjunmalik/Shipsy/query-builder/test2.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47

Comments

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.