2

I have a JavaScript function that generates an endless slideshow. The code works, but I'm concerned about each iteration ending in a recursive call to itself. Using developer tools, it appears that the call stack is growing.

I tried removing the recursive call to show_slides and wrapping the body of the function in a while (true) loop, but this caused the browser to lock up, burning lots of CPU and displaying nothing.

I assume I've missed waiting for the resolution of a Promise somewhere, and have tried appending .then(() => {}) to both the inner and the outer chains, without success.

Assuming that this is growing the stack (infinitely), rather than being treated as tail recursion and actually executing as a loop, what do I need to turn the body of the function into a loop?

Update: screenshot of Chrome dev tools call stack after a few iterations:

Call stack

const duration = 2000; // time (msec) to display each slide
const sizes = [
  [4000, 500],
  [1000, 4000],
  [600, 400],
  [100, 200],
  [4000, 4000]
];
const sleep = ms => new Promise(r => setTimeout(r, ms));
let n = 0;

function show_slides(duration) {
  const my_parent = document.querySelector('#slide-div');
  let size_index = n++ % sizes.length;
  let w = sizes[size_index][0];
  let h = sizes[size_index][1];

  let my_randomizer = `https://placehold.co/${w}x${h}?text=${w}+x+${h}\npx`;
  fetch(my_randomizer)
    .then(my_response => my_response.blob())
    .then(my_blob => {
      let my_url = URL.createObjectURL(my_blob);
      sleep(duration)
        .then(() => {
          URL.revokeObjectURL(my_parent.querySelector('img').src);
          my_parent.querySelector('img').src = my_url;
          // A recursive call to iterate??
          show_slides(duration);
        });
    })
    .catch(my_error => console.error('Error: ', my_error));
}
* {
  box-sizing: border-box;
}

html {
  height: 100%;
  width: 100%;
}

body {
  background-color: #dbd2c3;
  font-family: Arial, Helvetica, sans-serif;
  /* prevent body from displacing */
  margin: 0;
  /* body should perfectly superimpose the html */
  height: 100%;
  width: 100%;
}

.outer-div {
  display: flex;
  flex-flow: column;
  height: 100%;
  /* Now create left/right margins */
  margin: 0 0;
}

.inner-fixed-div {
  margin-top: 0.5em;
}

.inner-remaining-div {
  margin-bottom: 0;
  flex-grow: 1;
  /* hints the contents to not overflow */
  overflow: hidden;
}

.picture-div {
  /* force the div to fill the available space */
  width: 100%;
  height: 100%;
  /* center child elements */
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.picture-div-img {
  max-width: calc(100% - 0.5em);
  /* included 0.5em * 2 margin on parent */
  max-height: calc(100% - 0.5em);
  /* included 2em margin on parent, may need adjust this further */
  border: 2px solid black;
}
<!DOCTYPE html>
<html lang="en">
<!-- Self-contained slideshow demo -->

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

</head>

<body onload="show_slides(duration);">
  <div class="outer-div">
    <div class="inner-fixed-div">
      <h1>Lorem Ipsum</h1>
    </div>
    <div class="inner-remaining-div">
      <div id="slide-div" class="picture-div">
        <!-- Placeholder <img> element for slides -->
        <img class="picture-div-img">
      </div>
    </div>
  </div>
</body>

</html>

12
  • 1
    It's not really recursion if you call it from .then(), since that's asynchronous. Commented Oct 1, 2024 at 17:26
  • The while call is to fast and locks the screen. now if you could sleep 10 ms after each loop then you wont have an issue. now fetch operation could count as sleep and that is why it work as it is a promise Commented Oct 1, 2024 at 17:35
  • 1
    @Barmar recursion is a function calling itself. the timing doesn't matter Commented Oct 1, 2024 at 17:46
  • 1
    @ITgoldman …but there is no function calling itself. There's only a .then() callback function calling the show_slides function. And yet, we think about this as a (pseudo-)recursive approach, because the concept of recursion is the tool that lets us understand how the code behaves. Commented Oct 1, 2024 at 18:23
  • 1
    "Using developer tools, it appears that the call stack is growing." - does it? Can you post a screenshot of what led you to this assumption? It should not grow the call stack (apart from maybe some clever devtools debugging utilities showing you an "async stack"). Commented Oct 1, 2024 at 18:27

1 Answer 1

3

See Why does setTimeout() clutter my call stack under Chrome DevTools? – the Chrome developer tools automatically capture an asynchronous “stack trace” for debugging. Your code is correct, and the call stack isn’t actually growing. (I’m not sure exactly how the developer tools deal with infinite growth, but they probably truncate somewhere; a stack overflow would be a bug in the developer tools.)

Still, as long as you can rely on support for async/await, you can get both a cleaner debugging experience and cleaner code:

const duration = 2000; // time (msec) to display each slide
const sizes = [
  [4000, 500],
  [1000, 4000],
  [600, 400],
  [100, 200],
  [4000, 4000],
];
const sleep = ms => new Promise(r => setTimeout(r, ms));

async function show_slides(duration) {
  const my_img = document.querySelector('#slide-div img');
  let n = 0;

  while (true) {
    const size_index = n++ % sizes.length;
    const [w, h] = sizes[size_index];
  
    let my_randomizer = `https://placehold.co/${w}x${h}?text=${w}+x+${h}\npx`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      const my_url = URL.createObjectURL(my_blob);
      URL.revokeObjectURL(my_img.src);
      my_img.src = my_url;
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;  // or continue after a delay
    }
    
    await sleep(duration);
  }
}
* {
  box-sizing: border-box;
}

html {
  height: 100%;
  width: 100%;
}

body {
  background-color: #dbd2c3;
  font-family: Arial, Helvetica, sans-serif;
  /* prevent body from displacing */
  margin: 0;
  /* body should perfectly superimpose the html */
  height: 100%;
  width: 100%;
}

.outer-div {
  display: flex;
  flex-flow: column;
  height: 100%;
  /* Now create left/right margins */
  margin: 0 0;
}

.inner-fixed-div {
  margin-top: 0.5em;
}

.inner-remaining-div {
  margin-bottom: 0;
  flex-grow: 1;
  /* hints the contents to not overflow */
  overflow: hidden;
}

.picture-div {
  /* force the div to fill the available space */
  width: 100%;
  height: 100%;
  /* center child elements */
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.picture-div-img {
  max-width: calc(100% - 0.5em);
  /* included 0.5em * 2 margin on parent */
  max-height: calc(100% - 0.5em);
  /* included 2em margin on parent, may need adjust this further */
  border: 2px solid black;
}
<!DOCTYPE html>
<html lang="en">
<!-- Self-contained slideshow demo -->

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

</head>

<body onload="show_slides(duration);">
  <div class="outer-div">
    <div class="inner-fixed-div">
      <h1>Lorem Ipsum</h1>
    </div>
    <div class="inner-remaining-div">
      <div id="slide-div" class="picture-div">
        <!-- Placeholder <img> element for slides -->
        <img class="picture-div-img">
      </div>
    </div>
  </div>
</body>

</html>

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

4 Comments

Great answer. And I prefer your version of the algorithm to mine. I was lost in the strange (to me) syntax. Probably very advisable to avoid arrow notation until more experience with async stuff.
@Chap: For what it’s worth, I’d say understanding the idea of passing around and scheduling functions is more fundamental to learning JavaScript.
Sorry, don't get your gist. More fundamental than what?
@Chap: Than async/await.

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.