You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 20:18:19