You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>)
}

问题根源

  1. useEffect重复绑定事件:useEffect依赖props.user,当props.user变化时会重复执行,导致多次绑定unload事件,用户离开时会触发多次POST请求,计数器被多次递减。
  2. POST请求未正确响应:后端POST接口没有返回响应,浏览器可能会因请求未完成重试,导致计数器多次递减。
  3. fetch在unload事件中的可靠性问题:unload事件中普通fetch可能被浏览器取消,导致请求未发送,或者浏览器为了兼容发送重复请求。
  4. 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,提问作者Владислав Король

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 05:05:17