Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Promisify this recursive function

Below is a simple recursive function that takes in a length, and decrements it using setTimeout. Once the length is <= 0, it's done.

How can I write this function (in pure JavaScript) so that I can use it like so:

animate(999).then(...)

const animate = length => {
  console.log(length)
  length -= 10
  if (length <= 0) {
    length = 0
    return
  }
  setTimeout(() => {animate(length)}, 10)
}

animate(999)

Update:

This is what I've tried. The problem I'm having is that it seems resolve is either not being called, or is being called on a different promise.

const animate = length => {
  return new Promise((resolve, reject) => {
    console.log(length)
    length -= 10
    if (length <= 0) {
      length = 0
      resolve(true)
      return // without this the function runs forever
    }
    setTimeout(() => {animate(length)}, 10)
  })
}

animate(999).then(result => console.log(result))

** Working Update (but don't understand it) **

const animate = length => {
  return new Promise((resolve, reject) => {
    console.log(length)
    length -= 10
    if (length <= 0) {
      length = 0
      return resolve(true)
    }
    setTimeout(() => {resolve(animate(length))}, 10)
  })
}

animate(999).then(result => console.log(result))
like image 702
Raphael Rafatpanah Avatar asked Jan 04 '23 08:01

Raphael Rafatpanah


2 Answers

setTimeout(() => {animate(length)}, 10) will not work, this might call the animate function again but never resolve the promise created in the outermost invocation (this is what your "working update" fixes - it resolves the outer promise with the promise from the recursive call, which will cause them to fulfill with the same result).

Instead of messing around with callbacks that much, promisify the asynchronous primitive that you are using, setTimeout:

function wait(t) {
    return new Promise(resolve => {
        setTimeout(resolve, t);
    });
}

and then use that in your animation function to always return a promise:

const animate = length => {
  console.log(length)
  length -= 10
  if (length <= 0) {
    length = 0
    return Promise.resolve()
  }
  return wait(10).then(() => {
    return animate(length)
  })
}
like image 121
Bergi Avatar answered Jan 06 '23 23:01

Bergi


A new Promise and consequently its resolve and reject is being generated every time, because you are returning a new Promise on every setTimeout function call and only resolving the latest one (the one inside the if (length <= 0)).

If you had placed the Promise((resolve, reject) over the function it would have worked fine.

Either that or you carry the resolve, reject with you.

like image 42
zurfyx Avatar answered Jan 06 '23 21:01

zurfyx