4

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))
Raphael Rafatpanah
  • 19,082
  • 25
  • 92
  • 158

4 Answers4

4

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)
  })
}
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
2

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.

zurfyx
  • 31,043
  • 20
  • 111
  • 145
2

Just for fun, you can do this. You shouldn't though. The code is beyond unmaintainable.

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

animate(999).then(result => console.log(result))
Sergiu Paraschiv
  • 9,929
  • 5
  • 36
  • 47
1

This is an excellent job for async/await:

let wait = ms => new Promise(resolve => setTimeout(resolve, ms));

const animate = async length => {
  for (; length > 0; length -= 10) {
    console.log(length);
    await wait(10);
  }
  return 0;
}

animate(999).then(() => console.log("Done!"));
jib
  • 40,579
  • 17
  • 100
  • 158