Search code examples
javascriptnode.jsrecursionasync-awaitcancellation

Stop a recursive function which uses setTimeout


I have the following template in my project:

function sleep (ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function foo (x) {
  await sleep(3000)
  await asyncTask()
  await foo()
}

It should repeat the job (foo) until a specific event is received. However, I am having a hard time finding a way to stop the recursion. Seems like there is no way to achieve it with this approach.

In some other, I could not find an answer/comment to describe how to terminate this kind of function.

One potential way would be using setInterval with an async function, but seems like setInterval is supposed to work with sync callbacks only.

(I've seen there are some npm packages for this particularly, but I am trying to avoid adding a new dependency just for this)


Edit: Seems like this solution also works:

let timeoutId

async function loop (socket) {
  await asyncTask()

  timeoutId = setTimeout(
    async () => await loop(socket),
    3000
  )
}

socket.on('event', () => clearTimeout(timeoutId))

Solution

  • The usual mechanism is to check some flag that can be set from the outside:

    // outside code watching for some event can set this flag to true
    // to stop further recursion
    let stopRecursion = false;
    
    function sleep (ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    async function foo (x) {
      await sleep(3000)
      await asyncTask()
      if (!stopRecursion) {
          await foo()
      }
    }
    

    Or, this can be built into an object passed to foo() to make it a more self-contained implementation:

    function sleep (ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    async function foo (x, options) {
      await sleep(3000)
      await asyncTask()
      if (!options.stopRecursion) {
          await foo(x, options)
      }
    }
    

    And, the outside code can set options.stopRecursion to true when it wants the recursion to stop.


    In case you're wondering, Javascript doesn't have any mechanism from outside the function to cancel the recursion. So, you have to build some test into the recursion itself (checking a variable, property or some external state) and then you can mutate that state when you want it to stop recursing.


    An object oriented approach, would make foo() be a method on an object and you could then have another method on the object called cancelRecursion that would set a boolean on the object that foo would check before recursing. So, there's lots and lots of ways to structure this.