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

React中Promise为何无法获取外部isPlayingClips状态的更新?

背景说明(便于理解问题)

我有一个多维数组,存储了要播放的视频片段的开始和结束时间。我遍历这些时间,播放视频并等待其到达结束时间(通过Promise实现),此时resolve Promise即可播放下一段视频。

代码中失效的部分是:Promise内部有一个if语句持续检查isPlayingClips状态,当状态变为false时,应提前resolve Promise并返回"cancelled"。我能看到useEffect内的状态已更新,但Promise内的状态从未变化。

示例代码

以下是整个依赖isPlayingClips状态的useEffect:

const [isPlayingClips, setIsPlayingClips] = useState<boolean>(false); 

useEffect(() => {
    // 这里能正确打印状态更新,但Promise内部看不到变化
    console.log("isPlayingClips updated:", isPlayingClips);

    async function playClips() {
      // 返回类似这样的数组: [[57.98, 62.98], [114.96, 119.96]]
      let clips = getAllClips();

      for (let i = 0, n = clips.length; i < n; ++i) {
        // 片段的开始和结束时间
        let start = clips[i][0];
        let end = clips[i][1];

        // 跳转到片段开始位置并播放
        updateVideoTime(start);
        player.play();

        // 播放片段直到到达结束时间或播放被取消
        let cp = await new Promise((resolve) => {
          // isPlayingClips始终是Promise创建时的true值,状态变为false时这里也不会更新
          const u = () => {
            console.log(player.currentTime, end, isPlayingClips);

            // 如果isPlayingClips变为false,提前resolve Promise
            if (!isPlayingClips) {
              console.log("CANCELLING PLAYING CLIPS");
              player.removeEventListener("timeupdate", u);
              resolve("cancelled");
            }

            // 到达结束时间时resolve Promise
            if (player.currentTime >= end) {
              player.pause();
              player.removeEventListener("timeupdate", u);

              resolve("finished");
            }
          };

          player.addEventListener("timeupdate", u);
        });

        // 如果Promise被取消,停止继续播放后续片段
        if (cp == "cancelled") return;
      }
    }

    if (isPlayingClips) {
      playClips();
    } else {
      player.pause();
    }
  }, [isPlayingClips]);
问题

我的写法是否有误?为何状态变更无法在Promise内部体现?


解决方案

问题出在闭包捕获了状态的旧值:当useEffect因为isPlayingClips更新重新执行时,之前启动的playClips函数是在旧的渲染周期中创建的,它捕获的isPlayingClips是当时的固定值(初始为true),后续状态更新后,这个函数内部的变量不会同步更新。

修复步骤:

  1. 使用useRef跟踪最新的状态值,因为ref的current属性是可变的,不会被闭包锁定,能跨渲染周期保持最新状态。
  2. 在useEffect中同步ref与状态值。
  3. 在事件监听函数中使用ref的current属性获取最新状态。
  4. 添加清理函数,避免残留的事件监听导致内存泄漏或错误。

修改后的代码:

const [isPlayingClips, setIsPlayingClips] = useState<boolean>(false); 
// 创建ref跟踪最新状态
const isPlayingClipsRef = useRef(isPlayingClips);

// 同步ref与状态值
useEffect(() => {
  isPlayingClipsRef.current = isPlayingClips;
}, [isPlayingClips]);

useEffect(() => {
    console.log("isPlayingClips updated:", isPlayingClips);
    let currentListener: (() => void) | null = null;

    async function playClips() {
      let clips = getAllClips();

      for (let i = 0, n = clips.length; i < n; ++i) {
        let start = clips[i][0];
        let end = clips[i][1];

        updateVideoTime(start);
        player.play();

        let cp = await new Promise((resolve) => {
          currentListener = () => {
            console.log(player.currentTime, end, isPlayingClipsRef.current);

            // 使用ref.current获取最新状态
            if (!isPlayingClipsRef.current) {
              console.log("CANCELLING PLAYING CLIPS");
              player.removeEventListener("timeupdate", currentListener!);
              resolve("cancelled");
            }

            if (player.currentTime >= end) {
              player.pause();
              player.removeEventListener("timeupdate", currentListener!);
              resolve("finished");
            }
          };

          player.addEventListener("timeupdate", currentListener);
        });

        if (cp == "cancelled") return;
      }
    }

    if (isPlayingClips) {
      playClips();
    } else {
      player.pause();
      // 清理当前的事件监听
      if (currentListener) {
        player.removeEventListener("timeupdate", currentListener);
      }
    }

    // 组件卸载或状态更新时清理监听
    return () => {
      if (currentListener) {
        player.removeEventListener("timeupdate", currentListener);
      }
    };
  }, [isPlayingClips]);

额外说明:

  • 用useRef保存状态是解决React闭包陷阱的常用方案,它能让你在异步操作或事件监听中获取最新的状态值。
  • 清理函数的作用是在useEffect重新执行或组件卸载时,移除之前添加的事件监听,避免多个监听同时存在导致的逻辑混乱和内存泄漏。

内容的提问来源于stack exchange,提问作者spindi598

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 18:15:37