Pensé que tenía el truco de las promesas en todas las situaciones, pero estoy atascado aquí: me gustaría que una animación sucediera con un temporizador. Lo único que puedo obtener del paquete de gráficos es el número de cuadro, por lo que calculo un cuadro final en función de una estimación de velocidad de cuadro.
El paquete de gráficos es p5.js , que expone frameCount
, que solo se ejecuta a unos 60 fps en cada ciclo de dibujo. (Lo nombré currentFrame en esta pregunta, con la esperanza de que eso se aclarara). Por lo que puedo decir, esta es la única visibilidad que tengo en el estado del cuadro de animación en 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 } }
Lo que me desconcierta es cómo darle a la persona que llama a doAnimation una promesa que se resuelva en el otro método. He intentado esto:
doAnimation(duration) { // animation stuff this.endFrame = currentFrame + (60 * duration); this.animationPromise = new Promise(resolve => { // I realize this is wrong, but it illustrates the problem. How do I run this test later? 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 // since resolve isn't an instance method of a promise } }
¿Alguien puede deshacerme de esto?
En lugar de almacenar la promesa de resolución, debe almacenar la resolución para llamar:
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(); } }
Por supuesto, debe asegurarse de que solo haya una llamada a doAnimation()
a la vez, y que no se llame a draw( draw()
antes doAnimation()
(o más precisamente, que onAnimationFinished
esté configurado cuando se configure un endFrame
). También es posible que desee restablecerlos (a undefined
) una vez que se alcance el marco final.
En general, el cumplimiento o el rechazo de una promesa debe hacerlo el proceso que se inició dentro de la función ejecutora que aprobó new Promise
. Es muy raro necesitar cumplir o rechazar la promesa desde fuera (al menos, de manera que la persona que llama se dé cuenta de que es un cumplimiento de promesa).
Yo usaría una cola de devolución de llamada:
Mantenga una lista de animaciones pendientes que tenga el cuadro en el que terminan y una devolución de llamada.
Haga que draw
busque en la cola las devoluciones de llamada de animación que debería llamar en función del (nuevo) currentFrame
.
Haga que el código doAnimation
en cola una devolución de llamada
Apenas:
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 } } }
El motivo del comentario done: () => resolve()
es que, en general, trato de evitar exponer directamente las funciones de resolve
/ reject
de la promesa al código fuera del ejecutor de la promesa, sobre la base de que el código fuera de él normalmente no debería ser directamente en el control de lo que sucede con la promesa. (Hago una excepción para setTimeout
. :-) ) Pero eso puede ser excesivo aquí.
Como alternativa, también puede asignar para draw
dentro de la devolución de llamada del constructor de la promesa:
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 */ });