0

I have an app that animates a value. Below, if to is set, the amount lerps to it.

const lerp = (v0, v1, t) => {
  return (1 - t) * v0 + t * v1;
}

const app = {
  to: false,
  amount: 20,
  animate(){
    requestAnimationFrame(this.animate.bind(this));
    if(this.to !== false){
       this.amount = lerp(this.amount, this.to, 0.1)
       if(Math.abs(this.amount - this.to) < 0.001){
         this.amount = this.to;
         this.to = false;
       }
       console.log(this.amount);
    }

  },
  init(){
    this.animate();
  }
}

app.init();
console.log("Waiting to start");
setTimeout(() => {
  console.log("Started!");
  app.to = 0;
}, 1000)

This works great. But I'd like to call a function when it finishes the process, and that function may change. Ideally, I'd like to add it like so:

...
  promise: null,
  animate(){
    requestAnimationFrame(this.animate.bind(this));
    if(this.to !== false){
       this.amount = lerp(this.amount, this.to, 0.1)
       if(Math.abs(this.amount - this.to) < 0.001){
         this.amount = this.to;
         this.to = false;
         // Resolve the promise so logic can continue elsewhere
         if(this.promise) this.promise.resolve();
       }
    }
    console.log(this.amount);
  },
  stop(){
    this.promise = something... // Not sure what this should be
    await this.promise;
    // subsequent logic
    nextFunction()
  }

I can't get my head around how I can properly set this up. Any help welcome.

Djave
  • 8,595
  • 8
  • 70
  • 124
  • 2
    Animation note when showing runnable snippets: always make your animation _stop_ when the thing you're trying to show off has occurred. Perpetual console logging isn't great for debugging (plus it ends up locking up people's browsers, which isn't super great either =). Rather than first scheduling the next frame, do the work, then -if still necessary- schedule the next frame. – Mike 'Pomax' Kamermans Jul 07 '22 at 17:09
  • Given the `requestAnimationFrame()` call is not inside the condition, this process *never* stops. Don't do that. – Bergi Jul 11 '22 at 10:55
  • I was trying to simplify my example as much as possible... – Djave Jul 11 '22 at 10:57

2 Answers2

0

Wrapper entire function in promise and then resolve it

const lerp = (v0, v1, t) => {
  return (1 - t) * v0 + t * v1;
}

const app = {
  to: false,
  amount: 20,
  animate() {
    return new Promise(resolve => {
      const inner = () => {
                requestAnimationFrame(inner.bind(this));

        if (this.to !== false) {
          this.amount = lerp(this.amount, this.to, 0.1)
          if (Math.abs(this.amount - this.to) < 0.001) {
            this.amount = this.to;
            this.to = false;
            resolve()
          }
        }
        console.log(this.amount);
      }
      inner()
    })
  },
  init() {
    return this.animate();
  }
}

app.init().then(() => alert('over'));

setTimeout(() => {
  app.to = 0;
}, 1000)
Konrad
  • 21,590
  • 4
  • 28
  • 64
-1

In order to create a promise for an arbitrary action (rather than a promise for the whole animate loop like Konrad showed) I used something from this answer: https://stackoverflow.com/a/53373813/630203 to create a promise I could resolve from anywhere.

const createPromise = () => {
    let resolve, reject;
    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });
    return { promise, resolve, reject };
};

const lerp = (v0, v1, t) => {
  return (1 - t) * v0 + t * v1;
}

const app = {
  to: false,
  amount: 20,
  animateToPromise: null,
  animateToResolve: null,
  animate(){
    requestAnimationFrame(this.animate.bind(this));
    if(this.to !== false){
       this.amount = lerp(this.amount, this.to, 0.1)
       if(Math.abs(this.amount - this.to) < 0.001){
         this.amount = this.to;
         this.to = false;
         if(this.animateToPromise){
           this.animateToResolve();
           this.animateToPromise = null;
           this.animateToResolve = null;
         }
       }
       console.log(this.amount);
    }

  },
  init(){
    this.animate();
  },
  async animateTo(n){
    const { promise: animateToPromise, resolve: animateToResolve } =
            createPromise();
    this.to = n;
        this.animateToPromise = animateToPromise; 
    this.animateToResolve = animateToResolve;
    await this.animateToPromise;
    return true;
  }
}

app.init();


console.log("Waiting to start");

(async () => {
  setTimeout(async () => {
    console.log("Started!");
    await app.animateTo(0);
    console.log("Got to 0!");
    console.log("now for 20");
    await app.animateTo(20);
    console.log("done :)");
  }, 1000)
})();

This means I can queue my promises with a single animate function running.

Djave
  • 8,595
  • 8
  • 70
  • 124
  • There's no reason to store the `animateToPromise` anywhere, and no reason to use a `createPromise` function instead of simply calling `new Promise` directly. – Bergi Jul 11 '22 at 11:06
  • "*create a promise I could resolve from anywhere*" - this is a bad idea. Notice that your method breaks down as soon as anyone calls `animateTo()` multiple times without `await`ing each other – Bergi Jul 11 '22 at 11:07
  • @Bergi it wouldn't "break down" it would just overwrite it https://jsfiddle.net/4suqgfc0/ – Djave Jul 11 '22 at 11:32
  • Yes, and the promise returned by the previous call would never be resolved. – Bergi Jul 11 '22 at 11:47
  • It wouldn't be resolved, but nothing would be awaiting for it — I don't think I get your point maybe its best we go our separate ways. This works great for my simple animation engine, I can set values, wait for them to be set and move onto the next step of animation. – Djave Jul 11 '22 at 11:57