无法使用 setInterval 在异步函数中获取值 - JavaScript

Can't reach value in async function with setInterval - JavaScript

我正在尝试通过按钮开始自动拉取卡片,并在 croupierCount 达到 17 时停止,使用 setIntervalcroupierCount 的值随着下面的这个函数发生变化(<div> 显示这个计数),但是当我试图在函数内部达到这个值以停止间隔时,它的记录值为 0。你能帮我解决吗这个?

const TheGame = () => {

  const [croupierCount, setCroupierCount] = useState(0);
  const [croupierHand, setCroupierHand] = useState([]);

  const onStandHandler = () => {            // triggered with button

     const croupierInterval = async () => {
        
        let card = await fetchCard(deck);  // fetching new card with every iteration (works) 
        croupierHand.push(card);  // pushes fetched card (works)
    
        if (card[0].value === 'ACE') {
            setCroupierCount((prevCount) => prevCount + 11);
        }
    
        else if (card[0].value === 'JACK' || card[0].value === 'QUEEN' || card[0].value === 'KING') {
            setCroupierCount((prevCount) => prevCount + 10);
        }
        else {
            setCroupierCount((prevCount) => prevCount + Number(card[0].value));
        };         
                                                         
        // croupierCount is changing (I'm displaying it in div)

        console.log(croupierCount); // croupierCount = 0, I don't know why.

        if(croupierCount > 17) {
            clearInterval(startInterval);
        };
    }

    const startInterval = setInterval(croupierInterval, 1000);

  };
};

您似乎忽略了一个重点:在这里使用 const...

const [croupierCount, setCroupierCount] = useState(0);

... 使 croupierCount 成为 常数 值,无论调用 setCroupierCount 多少次。这个变量不能直接更新:你看到的它的更新实际上是 React 内部状态的变化,当组件被重新渲染时用相同的名称表示 - 渲染函数被再次调用。

这种不变性对于基于钩子的函数式组件来说既是福也是祸。


这是这里发生的事情:

  • 渲染组件时,TheGame 函数被第一次调用。它的 useState 调用初始化值和相应的 setter 作为绑定到该组件实例的内部 React 状态的一部分。

这些值从 useState 函数返回 - 并存储在 TheGame 函数、croupierCountsetCroupierCount 的局部变量(常量!)中。重要的 - 并且经常被遗漏的 - 是这些特定变量会在每次 TheGame 函数被调用时重新创建 !

  • 然后 onStandHandler 函数被创建,上述两个局部变量都作为其范围的一部分可用。

  • 在某些时候,onStandHandler 函数被触发(当用户按下按钮时)。它创建了另一个函数,croupierInterval,它应该首先获取数据,然后通过调用 setCroupierCount 来更新状态。

不过这个函数有两个问题。

首先,所有 croupierInterval 看到的是 current croupierCountsetCroupierCount 变量的值。当重新渲染被触发并且 TheGame 函数下次执行时,它不能神奇地 'peek' 这些变量将携带哪些值 - 因为这些实际上将是新变量!

但是您似乎忽略了一个更大的问题:setInterval 不能很好地与 fetch(或任何异步操作)配合使用。不用等待那个action的处理,你只要让JS周期性的触发这个函数即可。

这不仅会造成预期的延迟(减慢 fetch 使其需要 10 秒,然后看看会发生什么),而且这里还有一个实际的错误:因为 clearInterval(startInterval) 没有不要停止处理 await fetchCard(deck) 之后的函数的所有部分,在最坏的情况下,您的荷官可能会超过 17。


这是 'why' 的部分,但是 'how to fix' 的内容是什么?这里有几件事值得尝试:

  • 避免像瘟疫一样在功能组件中使用 setInterval:通常有更好的替代品。特别是在这种情况下,您至少应该将设置调用 croupierInterval 与上一个调用的完成

    联系起来
  • useEffect 每当你想要一些东西作为某种副作用间接修改你的状态时。这不仅使您的代码更易于阅读和理解,而且还可以让您清除副作用的副作用(如 timeouts/intervals 集)

  • 也不要忘记处理人为错误:如果用户错误地双击此按钮会发生什么?

如果有人遇到类似问题,我会发布我的解决方案。

const isMount = useRef(false);

...

useEffect(() => {
  
    if (isMount.current && croupierCount !== 0) {
        
        if (croupierCount < 17) {
            setTimeout(() => {
                onStandHandler();
            }, 1000);
        }
    }

    else isMount.current = true;
}, [croupierCount]);

const onStandHandler = async () => {

        let card = await fetchCard(deck, 1);
        croupierHand.push(card);
    
        if (card[0].value === 'ACE') {
            setCroupierCount(prevCount => prevCount + 11);
        }
    
        else if (card[0].value === 'JACK' || card[0].value === 'QUEEN' || card[0].value === 'KING') {
            setCroupierCount(prevCount => prevCount + 10);
        }
        else {
            setCroupierCount(prevCount => prevCount + Number(card[0].value));
        };    
};