将基本轮询变为长轮询

Turn basic polling into long polling

我正在开发一个聊天应用程序,到目前为止,我每两秒轮询一次我的服务器以获取消息。为了使聊天更加即时,我想实施长轮询。我正在尝试为此实现此 JS.info Guide,但我一直没有达到我打算达到的目标。谁能为我指出这一点?

我在 React 中有一个自定义挂钩,目前是基本轮询。

仅供参考:我是初学者,现在不会使用 WS 或 Socket.io。在这一点上对我来说太多了。

提前致谢!

export default function useGetChatMessages(_id) {
  const [chatMessages, setChatMessages] = React.useState([]);

  React.useEffect(() => {
    const interval = setInterval(() => {
      getChatMessages(_id).then(fetchedChatMessages => {
        setChatMessages(fetchedChatMessages);
      });
    }, 2000);
    return () => clearInterval(interval);
  }, [_id]);

  return chatMessages;
}

这是我的 express 服务器功能。我想它应该在这里实现而不是在我的钩子中实现

router.get('/:id/messages', async (request, response) => {
  try {
    const chat = await Chat.findById(request.params.id);
    response.json(chat.messages);
  } catch (error) {
    console.log(error);
    response.status(500).json({ message: error.message });
  }
});

长轮询基本上是一种保持 HTTP 连接打开的方法,因此您需要在服务器端实现它,您的前端无法控制服务器对请求执行的操作。

作为旁注,长轮询通常比 websockets 对服务器的负担要大得多,老实说,你的时间最好花在实现 websocket 连接上,如果你使用socket.io

等库

编辑:正如语义说明,长轮询是一种事件驱动的方式,服务器无需客户端发送特定请求即可向客户端发送响应,而常规轮询只是发送请求一个特定的时间间隔(听起来你已经知道了,因为你提到想让你的轮询更即时)。

如果您有兴趣改进常规轮询设置,请查看其他答案。

长轮询也需要更改后端。所以假设你不能更改服务器端代码,你仍然可以实现更全面的基本http轮询。
要执行基本的 http 轮询,您必须在最后一次成功调用后调用您的 API,因此您要确保只有一个 API 调用正在排队。

我还添加了一个粗略的 Exponential Backoff 以更好地处理错误。如果您有后端问题(例如中断),这将防止您的前端攻击您的服务器。

您的代码改编如下:

export default function useGetChatMessages(_id) {
  const [chatMessages, setChatMessages] = React.useState([]);

  React.useEffect(() => {
    let delay = 1000;
    let timeout = null;

    const updateChat = () => {
      getChatMessages(_id).then(fetchedChatMessages => {
        // you must also consider passing a timestamp to the API call
        // so you only fetch the latest messages, instead of all of them every time
        // your state update would look like this:
        // setChatMessages((messages) => [...messages, fetchedChatMessages]);
        setChatMessages(fetchedChatMessages);
        // reset the delay in case an error has happened and changed it.
        delay = 1000;
        // now call the API again after 1 second
        timeout = setTimeout(updateChat, delay);
      }).catch(error => {
        // exponential backoff here.
        // 1 - the first error will call the API after 2sec
        // 2 - the second error will call the API after 4 sec
        // 3 - the third error will call the API after 8 sec
        // and so on
        console.error("Could not update chat. waiting a bit...", error);
        delay = delay * 2;
        timeout = setTimeout(updateChat, delay);
      });
    }

    return () => timeout && clearTimeout(timeout)

  }, [_id]);

  return chatMessages;
}