Home > Net >  Create a promise here, resolve it there
Create a promise here, resolve it there

Time:02-15

I thought I had the hang of promises in all situations, but I'm stuck here: I'd like an animation to happen on a timer. The only thing I can get from the graphics package is the frame number, so I compute an end frame based on a frame rate guess, like this:

The graphics package is p5.js, which exposes frameCount, that just runs up at about 60 fps on each draw cycle. (I named it currentFrame in this question, hoping that would clarify). As far as I can tell, this is the only visibility I get into the animation frame state in p5.js

doAnimation(duration) {
  // animation stuff
  this.endFrame = currentFrame   (60 * duration); // 60fps seems to be a good guess
}

draw() {
  if (currentFrame < this.endFrame) {
    // compute and draw the state of the animation
  } else if (currentFrame >= this.endFrame) {
    // animation done
  }
}

What I'm puzzled by is how to give the doAnimation caller a promise that resolves in the other method. Something like this...

doAnimation(duration) {
  // animation stuff
  this.endFrame = currentFrame   (60 * duration);
  return new Promise(resolve => {
    // This doesn't work. I think it just runs at the start and doesn't resolve
    if (currentFrame >= this.endFrame) resolve();
  });
}

I've tried this...

doAnimation(duration) {
  // animation stuff
  this.endFrame = currentFrame   (60 * duration);
  this.animationPromise = new Promise(resolve => {
    if (currentFrame >= this.endFrame) resolve();
  });
  return this.animationPromise;
}

draw() {
  if (currentFrame < this.endFrame) {
    // compute and draw the state of the animation
  } else if (currentFrame >= this.endFrame) {
    this.animationPromise.resolve();  // also wrong, I think
  }
}

Can somebody get me un-mixed up about this?

CodePudding user response:

Instead of storing the promise to resolve, you need to store the resolver to call:

doAnimation(duration) {
  // animation stuff
  this.endFrame = currentFrame   (60 * duration);
  return new Promise(resolve => {
    this.onAnimationFinished = resolve;
  });
}

draw() {
  if (currentFrame < this.endFrame) {
    // compute and draw the state of the animation
  } else if (currentFrame >= this.endFrame) {
    this.onAnimationFinished();
  }
}

Of course you need to ensure that there is only one call to doAnimation() at a time, and that draw() is not called before doAnimation() (or more precisely, that onAnimationFinished is set up when an endFrame is set). You also might want to reset them (to undefined) once the end frame is reached.

CodePudding user response:

In general, fulfilling or rejecting a promise should be done by the process that was started within the executor function you passed new Promise. It's very rare to need to fulfill or reject the promise from outside it (at least, in a way that the caller realizes it's a promise fulfillment).

I'd use a callback queue:

  1. Maintain a list of pending animations which has the frame they end on and a callback.

  2. Have draw look through the queue for animation callbacks it should call based on the (new) currentFrame.

  3. Have the doAnimation code queue a callback

Roughly:

activeAnimations = [];

doAnimation(duration) {
    // animation stuff
    const endFrame = currentFrame   (60 * duration); // 60fps seems to be a good guess
    return new Promise(resolve => {
        this.activeAnimations.push({
            endFrame,
            done: resolve, // Or possibly: `done: () => resolve()`
                           // so this code is in control of whether
                           // there's a fulfillment value
        });
    });
}

draw() {
    for (let index = 0; index < this.activeAnimations;   index) {
        const animation = this.activeAnimations[index];
        if (currentFrame > animation.endFrame) {
            // Animation done
            animation.done();
            this.activeAnimations.splice(index, 1);
            --index;
        } else {
            // Continue animation
        }
    }
}

The reason for the done: () => resolve() comment is that in general, I try to avoid directly exposing the promise resolve/reject functions to code outside the promise executor, on the basis that code outside it shouldn't typically be directly in control of what happens to the promise. (I make an exception for setTimeout. :-) ) But that may be overkill here.

CodePudding user response:

As an alternative, you can also assign to draw within the promise constructor callback:

function basicDraw() {
    // ...
}
var draw = basicDraw; // Default function

doAnimation(duration) {
    // animation stuff
    this.endFrame = currentFrame   (60 * duration);
    return new Promise((resolve) => {
        draw = () => {
            basicDraw();
            if (currentFrame < this.endFrame) {
                // compute and draw the state of the animation
            } else if (currentFrame >= this.endFrame) {
                draw = basicDraw; // back to normal
                resolve();
            }
        };
    });
}

doAnimation(1000).then(() => {
     /* whatever you want to do next */ 
});
  • Related