React setState 和流响应

问题描述 投票:0回答:2

我如何处理流式响应?

使用

setState
的函数方法与函数似乎不起作用: 钩子没有按正确的顺序执行,并且响应是无序的

    const response = await fetch('/dostuff', {
      method:'POST',
      body
    })

    const reader = response.body!.getReader();
    const chunks = [];
    
    let done, value;
    while (!done) {
      ({ value, done } = await reader.read());
      if (done) {
        return chunks;
      }
      const strval = new TextDecoder().decode(value)
      chunks.push(strval);
      await setResponseText((prev)=> {
        return prev + strval
      })

      // console.log("EEE ",strval);          
    }
reactjs stream streaming
2个回答
0
投票

useEffect hook 用于启动获取过程,responseText 状态在循环结束时更新一次。这应该有助于避免异步状态更新的潜在问题。您可以添加任何依赖项,以便在它们发生更改时触发效果再次运行。

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('/dostuff', {
        method: 'POST',
        body,
      });

      const reader = response.body.getReader();
      let chunks = '';

      while (true) {
        const { value, done } = await reader.read();

        if (done) {
          break;
        }

        const strVal = new TextDecoder().decode(value);
        chunks += strVal;
      }

      setResponseText(chunks);
    };

    fetchData();
  }, []);

0
投票

最终问题有两个不同点:

  • while 循环中的解码器
  • 启用react严格模式并使用Effect交互。

解码器

解码器需要位于循环之外,因为流响应可能在多字节字符的中间结束。在每次迭代时创建新的解码器不允许管理这种情况。

严格模式

老实说,我不认为 useEffect 钩子以某种方式参与了这个错误,所以我限制了原始问题中的代码,我的错。

发生的情况是,在开发模式下,React严格模式会导致组件的渲染和安装/卸载两次。

这会造成竞争状况,因为当第二个渲染的流响应开始时,第一个渲染的流响应仍然处于活动状态并正在接收数据。

因此,包含响应文本的状态有时会使用第一个流数据块进行更新,有时会使用第二个流数据块进行更新。

因此,解决方案是在 useEffect 调用中添加 AbortController。

useEffect( ()=> {
  const controller = new AbortController();
  const run = async (controller:AbortController) => {   

    let response
    try {

      response = await fetch('/doStuff', {
        method:'POST',
        body
      })
      
    } catch (error) {
      if (error.name === "AbortError") {
        return;
      }
      ...
    }

    const reader = response.body!.getReader();
    const chunks = [];
    
    let done, value;
    const dec = new TextDecoder()

    while (!done) {
      ({ value, done } = await reader.read());
      if (done) {
        return chunks;
      }
      const strval = dec.decode(value, { stream: true })
      chunks.push(strval);
      text.current += strval
      setResponseText(text.current)      
    }


  }
  run(controller)

  return () => controller.abort();
  
},[])
© www.soinside.com 2019 - 2024. All rights reserved.