javascriptsettimeoutajax-polling

Periodically calling a function, stack growing indefinite


I have the following JavaScript code and noticed the stack is growing indefinitely.

https://jsfiddle.net/uok7vz9b/

(function () {
  async function poll() {
    try {
        // await fetch ...
    } catch (err) {
      console.error("Error: ", err);
    } finally {
      console.trace();
      start();
    }
  }

  function start() {
    setTimeout(poll, 2000);
  }

  document.addEventListener("DOMContentLoaded", poll, {once: true});
})();

First output is:

console.trace() _display:118:15
    poll https://fiddle.jshell.net/_display/?editor_console=true:118
    (Async: EventListener.handleEvent)
    <anonymous> https://fiddle.jshell.net/_display/?editor_console=true:127
    <anonymous> https://fiddle.jshell.net/_display/?editor_console=true:128

Second output:

console.trace() _display:118:15
    poll https://fiddle.jshell.net/_display/?editor_console=true:118
    (Async: setTimeout handler)
    start https://fiddle.jshell.net/_display/?editor_console=true:124
    poll https://fiddle.jshell.net/_display/?editor_console=true:119
    (Async: EventListener.handleEvent)
    <anonymous> https://fiddle.jshell.net/_display/?editor_console=true:127
    <anonymous> https://fiddle.jshell.net/_display/?editor_console=true:128

... and so on.

I don't really understand why, because I though setTimeout does not block and just starts a new timer. So I expected the poll function to finish, and then it is fired again with a clean stack.

I have looked around and not really found any good examples for "execute function once on page load and then periodically". I don't want to use setInterval because long running AJAX calls could then exceed the timeout. But the timeout should rather be the minimum time between two calls of poll.


Solution

  • This is a result of async stack traces. I have no idea if it's possible to blow the async stack or whether there are measures in the browser to limit the depth of the async stack preventing overflow/leaks.

    While the following code does the same thing:

    (function() {
      const delay = (duration) => new Promise(r => setTimeout(() => r(), duration))
      async function poll() {
        for (;;) {
          try {
            // await fetch ...
          } catch (err) {
            console.error("Error: ", err);
          } finally {
            console.trace();
          }
          await delay(2000)
        }
    
      }
    
    
      document.addEventListener("DOMContentLoaded", poll, {
        once: true
      });
    })();

    It does not add to the async stack.