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

React中为何无法通过Ref存储的resolve函数兑现Promise?

问题:自定义promisify Hook无法在状态更新后兑现Promise

我尝试创建一个promisify风格的Hook,目标是结合Redux Toolkit使用React 19的use Hook。以下是该Hook的核心实现:

function usePromise(input: {
    isLoading: true,
    data: null
} | {
    isLoading: false,
    data: string;
}) {
    // 存储resolve函数到ref中
    const resRef = useRef<(data: string) => void>(null);

    // 创建新Promise并将resolve函数存入ref
    const promiseRef = useRef(
        new Promise((res) => {
            resRef.current = res;

            // res("xxx") // 👈 这样调用可以正常resolve
        })
    );

    // 当input变化时,如果已有数据则兑现Promise
    useEffect(() => {
        if (!input.isLoading) {
            resRef.current?.(input.data);
        }
    }, [input]);

    // 返回Promise实例
    return promiseRef.current;
}

使用示例如下:

export function MyComponent() {
    const [value, setValue] = useState<null | string>(null);

    const prom = usePromise(value ? {
        isLoading: false,
        data: value
    } : {
        isLoading: true,
        data: null
    });

    prom.then((v) => alert(v))

    return <div>
        <button onClick={() => setValue("123")}>Click me</button>
    </div>
}

我预期点击按钮后,Promise会被兑现并弹出alert,但实际并未生效。


问题原因与解决方案

核心原因

  1. 渲染阶段执行副作用违规:你在组件的渲染阶段直接调用prom.then(...)执行alert这类副作用,违反了React渲染阶段必须是纯函数的规则。React会对渲染阶段的代码进行优化或批处理,导致回调无法按预期触发。
  2. 回调注册时机错误:每次组件渲染都会重新调用prom.then(...)注册新回调,当Promise最终被resolve时,这些在渲染阶段注册的回调执行时机被React的渲染机制干扰,导致alert无法正常弹出。

修复方案

方案1:将副作用移至useEffect中

把then的逻辑放到useEffect里,确保副作用在组件挂载或依赖变化时执行:

export function MyComponent() {
    const [value, setValue] = useState<null | string>(null);

    const prom = usePromise(value ? {
        isLoading: false,
        data: value
    } : {
        isLoading: true,
        data: null
    });

    useEffect(() => {
        prom.then((v) => alert(v));
    }, [prom]);

    return <div>
        <button onClick={() => setValue("123")}>Click me</button>
    </div>
}

方案2:结合React 19的use Hook使用(符合你的原始目标)

React 19的use Hook专门用于在渲染阶段读取Promise的值,配合useEffect处理副作用:

export function MyComponent() {
    const [value, setValue] = useState<null | string>(null);

    const prom = usePromise(value ? {
        isLoading: false,
        data: value
    } : {
        isLoading: true,
        data: null
    });

    // 使用React 19的use Hook读取Promise结果
    const data = use(prom);

    // 当数据可用时执行alert
    useEffect(() => {
        alert(data);
    }, [data]);

    return <div>
        <button onClick={() => setValue("123")}>Click me</button>
    </div>
}

额外优化:支持重复加载场景

原Hook的Promise实例在组件生命周期内唯一,无法支持“加载完成后再次进入加载状态”的场景。如果需要重复加载,可在input.isLoading为true时重新创建Promise:

function usePromise(input: {
    isLoading: true,
    data: null
} | {
    isLoading: false,
    data: string;
}) {
    const resRef = useRef<(data: string) => void>(null);
    const promiseRef = useRef<Promise<string>>();

    // 进入加载状态时,重新创建新的Promise
    if (input.isLoading) {
        promiseRef.current = new Promise((res) => {
            resRef.current = res;
        });
    }

    useEffect(() => {
        if (!input.isLoading && promiseRef.current) {
            resRef.current?.(input.data);
        }
    }, [input]);

    // 兜底返回已resolve的Promise,避免undefined
    return promiseRef.current ?? Promise.resolve("");
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 11:21:07