-1

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))
VIVID
  • 555
  • 6
  • 14
  • 1
    How is the "event" received? Can you access to the event from the function `foo`? – Santiago Rojo Jun 13 '23 at 14:34
  • @SantiagoRojo Yes, I can. It is a socketio client that receives and handles events. – VIVID Jun 13 '23 at 14:36
  • `asyncTask` doesn't seem to use the return. Perhaps it should? Then you probably wouldn't need `sleep`. – evolutionxbox Jun 13 '23 at 14:41
  • Do you need to cancel the promises if the event happens? – Santiago Rojo Jun 13 '23 at 14:42
  • @SantiagoRojo I need this cron task to refresh a specific token before it expires periodically unless the client disconnects. On the `disconnect` event, I need to stop refreshing the token. – VIVID Jun 13 '23 at 14:54
  • @evolutionxbox Does my previous comment clarify the situation a bit more? I may have abstracted my use case in the wrong way in the post. – VIVID Jun 13 '23 at 14:55
  • 2
    `if (Istillwanttocontinue) await asyncTask(); if (Istillwanttocontinue) await foo();` – trincot Jun 13 '23 at 19:01
  • 2
    Your latest approach would not work if the event occurs while `await asyncTask()` is awaiting because there would be no timer running yet. – jfriend00 Jun 14 '23 at 06:08

1 Answers1

1

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.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thanks for the answer and insights! I have also updated the description with another approach. – VIVID Jun 14 '23 at 05:55
  • 1
    Instead of a custom object, you could use an `AbortController` that can also be passed to Web APIs, and comes with nice other features. – Kaiido Jun 14 '23 at 06:29