Node.js全局计数变量异常:如何确保仅在用户离开页面时递减?
页面在线用户统计异常问题修复
我开发了一个统计网页同时在线用户数量的应用:前端React组件加载时向Node.js后端发送GET请求获取用户数并展示,用户离开页面时通过unload事件发送POST请求让计数器减1。但目前googleCounter变量行为完全不可控:服务器启动后数值直接落在-3到-5之间,每次触发GET请求时,计数器一边递增一边递减。另外在客户端尝试记录POST请求时,还会出现“事件监听器未定义”的错误。
后端代码(server.js)
//This is a small backend that keeps track of how many people are on each page. const express = require('express') const cors = require('cors') const app = express() const whiteList = ['http://localhost:3000', 'http://127.0.0.1:3000'] const corsOptions = { origin: function (origin, callback) { if (!origin || whiteList.indexOf(origin) !== -1) callback(null, true) else callback(new Error("Not allowed by CORS")) }, credentials: true, } app.use(cors(corsOptions)) let googleCounter = 0, discordCounter = 0, linuxCounter = 0 console.log(googleCounter, discordCounter, linuxCounter) app.get("/google", (request, response) => { console.log(googleCounter, discordCounter, linuxCounter) console.log(`Google counter ${googleCounter} increased to ${googleCounter + 1}`) googleCounter++ response.send(`Google Counter: ${googleCounter}`) }) app.get("/discord", (request, response) => { discordCounter++ response.send(discordCounter) }) app.get("/linux", (request, response) => { linuxCounter++ response.send(linuxCounter) }) //These methods will fire when users leave the pages. app.post("/google", () => { console.log(`Google counter ${googleCounter} decreased to ${googleCounter - 1}`) googleCounter--}) app.post("/discord", () => discordCounter--) app.post("/linux", () => linuxCounter--) app.listen(5000, () => console.log(`Server started on port 5000: ${googleCounter}, ${discordCounter}, ${linuxCounter}`))
前端代码(Google.js)
//This is a generic page to view. Source: https://uk.wikipedia.org/wiki/Google import React, {useState, useEffect} from "react" import Base from "./Base" export default function Google(props) { const url = "http://localhost:5000/google" const [counter, setCounter] = useState(1) //Increment user counter when the page is loaded: useEffect(() => { if (!props.user) return; fetch(url, {method: "GET"}) .then(response => {return response.text()}) .then(response => console.log(response)) .catch(error => console.log(error)) //Decrement user counter when the user leaves the page: window.addEventListener("unload", () => fetch(url, {method: "POST"}) .then(response => {return response.text()}) .then(response => console.log(response)) .then(() => console.log("User left the page"))) .catch(error => console.log(error)) }, [props.user]) if (!props.user) return (<div><Base/></div>) return (<div> <h1>Google</h1> <h2>Currently active users: {counter}</h2> </div>) }
问题根源
- useEffect重复绑定事件:
useEffect依赖props.user,当props.user变化时会重复执行,导致多次绑定unload事件,用户离开时会触发多次POST请求,计数器被多次递减。 - POST请求未正确响应:后端POST接口没有返回响应,浏览器可能会因请求未完成重试,导致计数器多次递减。
- fetch在unload事件中的可靠性问题:
unload事件中普通fetch可能被浏览器取消,导致请求未发送,或者浏览器为了兼容发送重复请求。 - catch位置错误:原代码中
catch绑定到了addEventListener的返回值上,而非fetch请求,导致出现“事件监听器未定义”的错误。
修复方案
前端调整
import React, {useState, useEffect} from "react" import Base from "./Base" export default function Google(props) { const url = "http://localhost:5000/google" const [counter, setCounter] = useState(1) useEffect(() => { if (!props.user) return; // 获取当前计数器并更新页面状态 fetch(url, {method: "GET"}) .then(response => response.text()) .then(text => { const count = parseInt(text.match(/\d+/)[0]) setCounter(count) }) .catch(error => console.log(error)) // 定义卸载时的请求处理函数 const handleUnload = () => { // 使用sendBeacon保证请求可靠发送,不会被浏览器中断 navigator.sendBeacon(url) } // 绑定事件监听 window.addEventListener("unload", handleUnload) // 清理函数:组件卸载时移除事件监听,避免重复绑定 return () => { window.removeEventListener("unload", handleUnload) } }, [props.user]) if (!props.user) return (<div><Base/></div>) return (<div> <h1>Google</h1> <h2>Currently active users: {counter}</h2> </div>) }
后端调整
const express = require('express') const cors = require('cors') const app = express() const whiteList = ['http://localhost:3000', 'http://127.0.0.1:3000'] const corsOptions = { origin: function (origin, callback) { if (!origin || whiteList.indexOf(origin) !== -1) callback(null, true) else callback(new Error("Not allowed by CORS")) }, credentials: true, } app.use(cors(corsOptions)) let googleCounter = 0, discordCounter = 0, linuxCounter = 0 app.get("/google", (request, response) => { googleCounter++ response.send(`Google Counter: ${googleCounter}`) }) // 修正POST接口:添加响应,防止计数器为负 app.post("/google", (request, response) => { if (googleCounter > 0) { googleCounter-- } response.status(200).send(`Google Counter: ${googleCounter}`) }) // 其他接口同理调整... app.listen(5000, () => console.log(`Server started on port 5000`))
内容的提问来源于stack exchange,提问作者Владислав Король
相关产品推荐
相关产品推荐

