Express+Firebase登录功能出现ERR_HTTP_HEADERS_SENT重复错误求助
解决Express+Firebase中ERR_HTTP_HEADERS_SENT错误
问题背景
开发官网登录/会员功能,基于Express.js结合Google Firebase实现账号系统,登录成功后多次刷新首页(点击官网图标重新加载),后端反复触发以下错误:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at new NodeError (node:internal/errors:372:5) at ServerResponse.setHeader (node:_http_outgoing:576:11) at ServerResponse.header (/Users/leziyang/Code/ttumc/node_modules/express/lib/response.js:794:10) at ServerResponse.contentType (/Users/leziyang/Code/ttumc/node_modules/express/lib/response.js:624:15) at ServerResponse.send (/Users/leziyang/Code/ttumc/node_modules/express/lib/response.js:149:14) at done (/Users/leziyang/Code/ttumc/node_modules/express/lib/response.js:1035:10) at tryHandleCache (/Users/leziyang/Code/ttumc/node_modules/ejs/lib/ejs.js:280:5) at View.exports.renderFile [as engine] (/Users/leziyang/Code/ttumc/node_modules/ejs/lib/ejs.js:491:10) at View.render (/Users/leziyang/Code/ttumc/node_modules/express/lib/view.js:135:8) at tryRender (/Users/leziyang/Code/ttumc/node_modules/express/lib/application.js:657:10)
错误不影响登出功能,但频繁出现影响开发体验。
错误原因分析
- onAuthStateChanged重复注册回调:每次访问首页(
/路由)都会调用onAuthStateChanged注册新的监听回调,当用户登录状态变化时,所有已注册的回调都会执行,导致多次调用res.render,试图多次向客户端发送响应头,触发错误。 - 全局变量signStatus的竞态问题:使用全局变量
signStatus存储登录状态,多请求并发场景下会被不同请求覆盖,不仅导致页面渲染状态不准确,还会加剧重复响应的问题。 - 异步操作未等待完成:注册用户时的
setDoc操作未使用await,可能在数据库操作未完成时就跳转,存在潜在的数据一致性问题。
修复方案
- 移除路由中
onAuthStateChanged的重复注册,改用getAuth().currentUser实时获取当前用户状态,避免多次回调执行。 - 删除全局变量
signStatus,每次请求时直接判断用户状态,避免竞态问题。 - 异步操作添加
await,确保数据库操作完成后再进行响应跳转。
完整修复代码
const express = require("express"); const bodyParser = require("body-parser"); const ejs = require("ejs"); const _ = require("lodash"); const app = express(); app.use(express.json()); app.set('view engine', 'ejs'); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static("public")); const { initializeApp } = require("firebase/app"); const { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut } = require("firebase/auth"); const { getFirestore, doc, setDoc } = require("firebase/firestore"); const credentials = require(__dirname + "/serviceAccountKey.json"); const initApp = initializeApp(credentials); const auth = getAuth(initApp); const db = getFirestore(initApp); // 首页路由:直接获取当前用户状态,无需重复注册监听 app.get("/", function (req, res) { const user = auth.currentUser; const signStatus = user ? 1 : 0; res.render("index", { signStatus: signStatus }); }); app.get("/login", function (req, res) { const user = auth.currentUser; const signStatus = user ? 1 : 0; res.render("login", { signStatus: signStatus }); }); app.post("/login", async function (req, res) { const email = req.body.email; const password = req.body.password; try { await signInWithEmailAndPassword(auth, email, password); res.redirect("/"); } catch (error) { res.send("<h1>登入失敗囉低能兒!</h1><br>"); } }); app.get("/signup", function (req, res) { const user = auth.currentUser; const signStatus = user ? 1 : 0; res.render("signup", { signStatus: signStatus }); }); app.post('/signup', async (req, res) => { const email = req.body.email; const password = req.body.password; const userName = req.body.userName; const schoolNumber = req.body.schoolNumber; const id = req.body.email; const userJson = { email: email, userName: userName, schoolNumber: schoolNumber, adminPower: false, }; try { const userCredential = await createUserWithEmailAndPassword(auth, email, password); await setDoc(doc(db, "users", id), userJson); res.redirect("/"); } catch (error) { const errorCode = error.code; const errorMessage = error.message; console.log(errorCode, errorMessage); res.send("<h1>創建失敗囉低能兒!</h1><br>" + errorMessage); } }); app.get('/signout', async function (req, res) { try { await signOut(auth); res.redirect("/"); } catch (error) { res.send("<h1>好像出了點問題 QAQ</h1>"); } }); app.listen(3000, function () { console.log("Server started on port 3000"); });
额外说明
auth.currentUser会实时返回当前登录的用户对象,未登录则为null,无需依赖监听回调即可获取状态。- 所有异步操作(
signInWithEmailAndPassword、createUserWithEmailAndPassword、setDoc、signOut)都改用async/await处理,代码逻辑更清晰,避免回调嵌套和未完成操作导致的问题。
内容的提问来源于stack exchange,提问作者RogerYueh
相关产品推荐
相关产品推荐

