数据流在 React 客户端中不起作用

Streaming of data not working in React client

提问人:MervS 提问时间:5/19/2023 最后编辑:MervS 更新时间:5/30/2023 访问量:494

问:

我目前正在开发一个 React 应用程序,该应用程序需要响应流。为了简化该方案,假设我有一个每秒返回一个数字的终结点。为此,我正在使用 Flask:

def stream():
    def generate():
        import math, time
        for i in range(10):
            yield f'{i}\n'
            time.sleep(1)

    return generate(), {"Content-Type": "text/plain"}

在我的 React 组件中,单击按钮将触发以下功能:

const readStream = () => {
  const xhr = new XMLHttpRequest();
  xhr.seenBytes = 0;
  xhr.open('GET', '/stream', true);
  xhr.onreadystatechange = function(){
    if (this.readyState === 3){
      const data = xhr.response.substr(xhr.seenBytes)
      console.log(data)
      setNumber(data)  // set state value that will be displayed elsewhere
      xhr.seenBytes = xhr.responseText.length;
    }
  }
  xhr.send();
}

出于某种原因,React 代码无法一次获取一个块,它只是一次转储所有数据。我已经验证了在 vanilla JS 中使用相同的 ajax 请求有效。我也尝试了不同的方法,包括使用,但到目前为止都没有奏效。fetchaxios

虽然我在这里使用,但我的实际用例需要并且我必须从该响应中获取我的数据。GETPOST

我错过了什么吗?

javascript reactjs ajax xmlhttprequest

评论


答:

-1赞 Sagar Vadnere 5/30/2023 #1

您似乎面临着使用 XMLHttpRequest 在 React 应用程序中接收流数据的问题。您描述的问题表明,数据不是分块接收的,而是一次性接收的

此行为的一个可能原因是 XMLHttpRequest 的 onreadystatechange 事件在流式处理过程中可能不会触发。相反,它会等到响应完成后再触发。若要解决此问题,可以修改代码以改用 xhr.onprogress 事件,该事件专门用于在接收数据时跟踪数据。

下面是使用 onprogress 事件的代码的更新版本:

const readStream = () => {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', '/stream', true);
  xhr.onprogress = function(event) {
    const data = xhr.response.substr(xhr.seenBytes);
    console.log(data);
    setNumber(data); // set state value that will be displayed elsewhere
    xhr.seenBytes = xhr.responseText.length;
  };
  xhr.send();
};

通过将 onreadystatechange 替换为 onprogress,您应该能够按预期以块形式接收流数据。

关于您提到的在实际用例中使用 POST,可以应用相同的方法。您需要相应地修改服务器端代码和客户端代码。

在服务器端 (Flask) 上,您将更改端点以接受 POST 请求:

@app.route('/stream', methods=['POST'])
def stream():
    # Your streaming logic here

在客户端 (React) 上,您将更新 xhr.open 方法以指定 POST 请求:

xhr.open('POST', '/stream', true);

通过这些修改,您应该能够使用 XMLHttpRequest 和 GET 和 POST 请求来接收流数据。

注意:考虑使用更现代的方法,如 Fetch APIAxios 在 React 中发出 HTTP 请求,因为它们为处理流数据提供了更简单、更灵活的选项。

更新:

const readStream = () => {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', '/stream', true);
  xhr.onprogress = function(event) {
    const data = xhr.response.substr(xhr.seenBytes);
    setTimeout(() => {
      setNumber(prevNumber => prevNumber + data); // concatenate the received chunk to the previous state value
    }, 0);
    xhr.seenBytes = xhr.responseText.length;
  };
  xhr.send();
};

在此更新的代码中,使用 setTimeout 函数的延迟为 0 毫秒。虽然这似乎有悖常理,但这种方法将状态更新推迟到事件循环的下一个时钟周期,允许 React 在收到每个块后渲染和更新 DOM。

通过以这种方式使用 setTimeout,您应该能够在 React 应用程序中实现所需的流式处理行为。

评论

0赞 MervS 5/30/2023
我也尝试过使用。从我尝试过的情况来看,如果我在 React 上下文之外(即在 vanilla JS 中)执行流代码,一切都会正常工作。但是当我将代码放在 React 中时,无论是使用 、 ,还是它不会流式传输,而是等待所有块加载后才能显示。onprogressxhrfetchaxios
0赞 Sagar Vadnere 5/30/2023
右!React 依赖于一个称为虚拟 DOM diffing 的概念,它通过批处理更新来优化渲染。此批处理可能会导致更新 DOM 延迟,从而导致您描述的行为,即在显示之前加载所有块。要解决此问题,您可以使用 setTimeout 函数在收到每个块后安排 React 状态的更新。通过这样做,您可以允许 React 独立处理每个块,而不是等待收到整个响应。我将使用 setTimeout 函数编辑我的答案。
0赞 MervS 5/31/2023
我已经尝试了代码,但仍然没有效果。我想知道我正在做的事情在 React 中是否可行,毕竟它似乎仍在等待进度循环完成
1赞 MervS 5/31/2023
好的,似乎在服务器中设置标头最终允许我一次加载一个块。Cache-controlno-transform
0赞 NotTheDr01ds 6/3/2023
这似乎很可能是由人工智能(例如 ChatGPT)编写的(全部或部分)。如果您使用 AI 工具来帮助回答此答案,我鼓励您将其删除。读者应仔细和批判性地阅读此答案,因为它可能包含根本错误和错误信息。如果您发现质量问题和/或有理由相信此答案是由 AI 生成的,请留下相应的反馈。这里禁止发布 AI 生成的内容,审核团队可以使用您的帮助来识别质量问题。