如何在requestAnimationFrame循环中等待Promise resolve执行后续逻辑
问题背景
我有一个用于实现数值动画的应用,如下方代码所示,当设置to属性时,数值amount会通过lerp(线性插值)逐步过渡到目标值。
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)
目前这段代码运行效果良好,但我希望在动画流程结束时调用指定函数,且该函数可能动态变化。理想情况下我希望按Promise逻辑改造代码,草稿如下:
... 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() }
不清楚该如何正确搭建这套Promise逻辑。
实现方案
你草稿写法的核心问题是:Promise实例本身不会对外暴露resolve方法,resolve只能在Promise构造函数的执行器回调中拿到,因此需要单独存储这个resolve引用,在动画结束时调用即可。
完整改造代码如下:
const lerp = (v0, v1, t) => { return (1 - t) * v0 + t * v1; } const app = { to: false, amount: 20, // 存储当前动画Promise的resolve方法 _animationResolve: null, // 存储当前进行中的动画Promise currentAnimation: 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 Promise,清空存储的引用 if(this._animationResolve){ this._animationResolve(); this._animationResolve = null; this.currentAnimation = null; } } console.log(this.amount); } }, // 统一的动画目标设置方法,返回可等待的Promise animateTo(target){ this.to = target; // 如果已有进行中的动画,直接返回现有Promise if(this.currentAnimation) return this.currentAnimation; // 新建Promise,存储resolve引用 this.currentAnimation = new Promise(resolve => { this._animationResolve = resolve; }); return this.currentAnimation; }, // 手动中断动画方法 stop(){ if(this._animationResolve){ this._animationResolve(); this._animationResolve = null; this.currentAnimation = null; this.to = false; } }, init(){ this.animate(); } } app.init(); console.log("Waiting to start"); setTimeout(async () => { console.log("Started!"); // 等待动画执行完成后再执行后续逻辑 await app.animateTo(0); console.log("Animation finished!"); nextFunction(); }, 1000) function nextFunction(){ console.log("Run next logic after animation"); }
关键说明
- 不要直接手动修改
to属性触发动画,统一调用animateTo(target)方法,才能正常返回可等待的Promise实例。 - 如果动画运行过程中需要切换目标值,现有逻辑会自动等待最终目标值到达后再resolve;如果需要切换目标时立即结束前序动画逻辑,可以在
animateTo方法开头判断如果存在_animationResolve,先执行resolve再创建新的Promise。 - 动画结束后要及时清空存储的resolve和Promise引用,避免重复触发或内存泄漏。
内容的提问来源于stack exchange,提问作者Djave
相关产品推荐
相关产品推荐

