Why should we interrupt the Promises current in recursive functions in Javascript?

Asked

Viewed 115 times

6

I will exemplify with codes.

I have an asynchronous function called delay which receives a time in seconds and which returns a Promise. It serves to provide a waiting time in seconds:

// padrão de 1 segundo
function delay (time = 1) {
  return new Promise((resolve) => {
    setTimeout(resolve, time * 1_000) // resolve depois de X segundos
  })
}

Now I’m going to create a clock function clock which will display the local time ( toLocaleTimeString) at each delay time (time) defined in function delay, and when this is resolved, I will display a log in the console of the current time. Then recursively invoke the function clock and return it:

function clock () {
  return delay(1).then(() => {
    // exibe a hora
    console.log(new Date().toLocaleTimeString()) 

    // inicia a recursão
    return clock() 
  })
}

Let’s run our clock:

// padrão de 1 segundo
function delay (time = 1) {
  return new Promise((resolve) => {
    setTimeout(resolve, time * 1_000) // resolve depois de X segundos
  })
}

function clock () {
  return delay(1).then(() => {
    // exibe a hora
    console.log(new Date().toLocaleTimeString()) 

    // inicia a recursão
    return clock() 
  })
}

// bootstrap
clock()

Using async/await:

// padrão de 1 segundo
function delay (time = 1) {
  return new Promise((resolve) => {
    setTimeout(resolve, time * 1_000) // resolve depois de X segundos
  })
}

async function clock () {
  await delay(1)
  
  // exibe a hora
  console.log(new Date().toLocaleTimeString()) 
  
  // inicia a recursão
  return clock() 
}

// bootstrap
clock()

Note that in both cases, the code works normally and the clock shows the updated time every second. However, the fact that I have a function that returns a Promise (returns delay and this returns an object Promise) and this Promise activates recursion, generates a current of Promises. This should be avoided!

  • Why?
  • What problem do I solve by interrupting the current of Promises caused by recursion in clock?
  • I found nothing related. If you found, feel free to signal. I think the question can contribute to the community :)

  • 1

    I found the question very interesting, especially for the +1 examples

  • 1

    I’ve never really seen this, but I suspect you already know the answer. I want a spoiler! I suppose it’s because of the gradual buildup of memory in call stack (I tried it here and it kind of happened), but I’m not sure... Anyway, there’s no gain in using recursion there. A simple while would already solve.

  • 1

    @Luizfelipe is that same. It has to do with memory Leak

  • Like Maniero says, working is different than being right. I question the need to use recursion in the case of the question, but I think it was an example to situate the problem. I follow the premise that, since leading Javascript Engines don’t even perform TCO, it’s almost never really worth using Javascript recursion. And as there is the equivalence between recursion and "common" ties, these are always preferable when the minimum performance (in this case mainly memory saving) is an important factor.

1 answer

3


You actually have memory leaking there, but the main reason is not the recursive structure - which in this case is asynchronous and indirect. The root of the problem is the behavior of the method then of promises.

Simplifying your code a little bit:

function clock () {
    console.log(new Date().toLocaleTimeString())
    return delay(1).then(clock)
}

You know you could chain another then at the end if you wanted to. That works because the then returns another promise. The status of this promise depends on the return of the function passed to then - which is the handler of an event triggered when delay(1) is resolved. The promise returned by then will be created already solved if the function returns any value (including undefined), or a resolved promise. The same logic holds for rejected promises. But if the function returns a pending promise, the then returns a new promise also pending, and that will only be resolved after the solution of this original promise.

Summarizing this whole mechanism: each implementation of clock creates a new promise, and postpones the resolution of then for the next execution. And this for the next, successively and infinitely. The chain of promises you quoted is a problem because it will never be solved.

This already indicates the solution: it is enough that clock does not return a pending promise. This however does not prevent you from continuing to chain calls. The following version no longer leaks memory:

function clock () {
    console.log(new Date().toLocaleTimeString())
    delay(1).then(clock)

    // Pode retornar qualquer coisa, ou pode omitir o return.
    // Omitir o return equivale a `return undefined`.
    // Não pode retornar a promessa gerada logo acima, ou outra que dependa dela
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.