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

React中状态更新未在异步WaitFor函数中生效的问题求助

问题原因分析

你遇到的是React状态的闭包陷阱:当waitForUnityInstanceToStart被调用时,内部的poll函数会捕获调用瞬间的unityInstanceIsStarted值(此时是false)。后续setUnityInstanceIsStarted(true)触发组件重新渲染后,会生成新的unityInstanceIsStarted变量,但之前已经启动的poll函数仍然引用旧的状态值,所以永远不会检测到状态变为true。

解决方案

方案1:使用useRef同步状态(最简单的轮询改进)

useRef的.current属性是可变的,闭包能始终访问到最新值,不会受组件重新渲染的影响:

import { useState, useRef, useEffect } from 'react';

const [unityInstanceIsStarted, setUnityInstanceIsStarted] = useState(false);
// 用ref同步状态,确保闭包能拿到最新值
const unityInstanceStartedRef = useRef(false);

useEffect(() => {
  // 状态更新时同步ref的值
  unityInstanceStartedRef.current = unityInstanceIsStarted;
}, [unityInstanceIsStarted]);

function waitForUnityInstanceToStart() {
  return new Promise((resolve) => {
    const poll = () => {
      if (unityInstanceStartedRef.current) {
        resolve();
      } else {
        setTimeout(poll, 500);
      }
    };
    // 立即执行一次检查
    poll();
  });
}

const waitForUnityInstanceToOpenProject = async (projectID) => {
  setEditorView(EditorView.Loading);
  await waitForUnityInstanceToStart();
  sendMessage(reactUnityRelay, "OpenProject", projectID);
};

方案2:用Promise订阅替代轮询(更高效)

避免定时轮询,直接监听状态变化触发Promise resolve,性能更好:

import { useState, useRef, useEffect } from 'react';

const [unityInstanceIsStarted, setUnityInstanceIsStarted] = useState(false);
// 保存Promise的resolve函数引用
const unityReadyResolver = useRef(null);
// 缓存已创建的Promise,避免重复创建
const unityReadyPromise = useRef(null);

useEffect(() => {
  // 当Unity实例启动且有等待的resolve时,触发完成
  if (unityInstanceIsStarted && unityReadyResolver.current) {
    unityReadyResolver.current();
    unityReadyResolver.current = null;
    unityReadyPromise.current = null; // 重置缓存,下次调用重新创建
  }
}, [unityInstanceIsStarted]);

function waitForUnityInstanceToStart() {
  // 如果已经启动,直接返回已完成的Promise
  if (unityInstanceIsStarted) {
    return Promise.resolve();
  }
  // 如果已有等待的Promise,直接返回
  if (unityReadyPromise.current) {
    return unityReadyPromise.current;
  }
  // 创建新的Promise并缓存
  unityReadyPromise.current = new Promise((resolve) => {
    unityReadyResolver.current = resolve;
  });
  return unityReadyPromise.current;
}

const waitForUnityInstanceToOpenProject = async (projectID) => {
  setEditorView(EditorView.Loading);
  await waitForUnityInstanceToStart();
  sendMessage(reactUnityRelay, "OpenProject", projectID);
};

额外优化:清理定时器(针对方案1)

如果用户可能在等待过程中取消操作,需要清理定时器避免内存泄漏,可以用AbortController:

function waitForUnityInstanceToStart(signal) {
  return new Promise((resolve, reject) => {
    let timerId;
    const poll = () => {
      // 如果信号已中止,拒绝Promise并清理定时器
      if (signal.aborted) {
        reject(new Error("Wait aborted"));
        return;
      }
      if (unityInstanceStartedRef.current) {
        resolve();
        clearTimeout(timerId);
      } else {
        timerId = setTimeout(poll, 500);
      }
    };
    poll();
    // 监听中止信号,清理定时器
    signal.addEventListener('abort', () => {
      clearTimeout(timerId);
    });
  });
}

// 使用示例
const waitForUnityInstanceToOpenProject = async (projectID) => {
  setEditorView(EditorView.Loading);
  const abortController = new AbortController();
  try {
    await waitForUnityInstanceToStart(abortController.signal);
    sendMessage(reactUnityRelay, "OpenProject", projectID);
  } catch (err) {
    console.log(err.message);
  }
  // 组件卸载时可以调用abortController.abort()清理
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 11:18:15