Node/Express服务接收fetch请求超时,跨域报错求助
Node/Express邮件服务CORS超时问题排查与修复
问题描述
基于Node/Express搭建邮件发送服务,通过nodemailer处理前端表单的fetch请求。点击提交后请求超时,浏览器报错:No 'Access-Control-Allow-Origin' header is present on the requested resource。已在服务端引入并配置cors(尝试过无选项、通配符"*"及指定站点源),邮件能正常发送,但客户端无法接收成功状态码。
原服务端代码
const express = require('express') const http = require('http') const cors = require('cors') require('dotenv').config() const mailer = require('nodemailer') const app = express() app.use(express.json()) app.use(express.urlencoded({extended: true})) app.use(cors({ origin: "*" })) //email middleware const transporter = mailer.createTransport({ service: "gmail", auth: { user: process.env.GAU, pass: process.env.GAP } }) async function sendEmail(emailAddress, emailSubject, emailBody, from="site"){ const options = { timeZone: 'America/Chicago', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }; const currentDate = new Date().toLocaleString('en-US', options); let message = "<h1>"+emailSubject+"</h1>" message += "<p>Email from contact form on myname."+from+"</p>" message += "<br><br>" message += "<h2>" + emailBody + "</h2>" message += "<br><br>" message += "<p>Reply to: "+emailAddress+"</p>" message += "<br><br>" message += "<p>At: "+currentDate+"</p>" const mailOptions = { form: process.env.GAU, to: process.env.CONTACT_EMAIL, subject: emailSubject + " | Contact Form", html: message } const mailOptions2 = { form: process.env.GAU, to: emailAddress, subject: "Thank you for reaching out!", html: "<em>Thank you for reaching out!</em><br><br>"+message } let successful = true transporter.sendMail(mailOptions, (error, info)=>{ if(error){ console.log('Error', error) successful = false } else{ console.log("Email sent: ", info.response) } }) transporter.sendMail(mailOptions2, (error, info)=>{ if(error){ console.log('Error', error) successful = false } else{ console.log("Email sent: ", info.response) } }) return successful } //routes app.get('/', (req,res)=>{ res.json('test ok') }) app.post("/sendEmailFromSiteForm",(req,res)=>{ const { emailAddress, emailSubject, emailBody } = req.body try { let result = sendEmail(emailAddress, emailSubject, emailBody, "site") if(result) res.status(200) else res.status(500) } catch (error) { res.status(500) } }) app.post("/sendEmailFromWorkForm",(req,res)=>{ const { emailAddress, emailSubject, emailBody } = req.body try { let result = sendEmail(emailAddress, emailSubject, emailBody, "work") if(result) res.status(200) else res.status(500) } catch (error) { res.status(500).json(error) } }) app.listen(8080 , ()=>{ 'Server started on port 8080.' })
原客户端代码
async function sendEmail(address, subject, body){ try { let response = await fetch('mysiteURL',{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ emailAddress: address, emailSubject: subject, emailBody: body }) }) if(response.ok){ console.log('email sent okay') } else{ console.log('Email failed to send.') } console.log(response.json()) } catch (error) { console.log('Email failed to send.') } }
问题根源与修复方案
1. 响应未发送(核心超时原因)
服务端路由仅设置了状态码res.status(200)/res.status(500),但未调用send()或json()发送响应,导致请求一直挂起超时,浏览器触发CORS错误(超时后浏览器默认抛出该错误,实际根源是无响应返回)。
2. 异步函数未等待
sendEmail是async函数,但路由调用时未加await,导致result为Promise对象而非布尔值,if(result)永远为true,且邮件发送逻辑未完成就提前执行响应判断。
3. nodemailer回调未正确处理异步
sendEmail中使用回调式transporter.sendMail,未等待两个邮件发送完成就返回successful,返回值不准确。改用await处理nodemailer的Promise支持。
4. 拼写错误
邮件配置中的form应为from,否则会导致邮件发送的发件人配置错误。
修复后的代码
服务端核心修复代码
// 重构sendEmail函数,用await处理异步 async function sendEmail(emailAddress, emailSubject, emailBody, from="site"){ const options = { timeZone: 'America/Chicago', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }; const currentDate = new Date().toLocaleString('en-US', options); let message = "<h1>"+emailSubject+"</h1>" message += "<p>Email from contact form on myname."+from+"</p>" message += "<br><br>" message += "<h2>" + emailBody + "</h2>" message += "<br><br>" message += "<p>Reply to: "+emailAddress+"</p>" message += "<br><br>" message += "<p>At: "+currentDate+"</p>" const mailOptions = { from: process.env.GAU, // 修复form拼写错误为from to: process.env.CONTACT_EMAIL, subject: emailSubject + " | Contact Form", html: message } const mailOptions2 = { from: process.env.GAU, // 修复form拼写错误为from to: emailAddress, subject: "Thank you for reaching out!", html: "<em>Thank you for reaching out!</em><br><br>"+message } try { // 等待两个邮件发送完成 await transporter.sendMail(mailOptions); await transporter.sendMail(mailOptions2); return true; } catch (error) { console.log('Error sending email:', error); return false; } } // 修复路由,添加async/await并发送响应 app.post("/sendEmailFromSiteForm", async (req,res)=>{ const { emailAddress, emailSubject, emailBody } = req.body try { let result = await sendEmail(emailAddress, emailSubject, emailBody, "site") if(result) { res.status(200).json({ message: "邮件发送成功" }); } else { res.status(500).json({ message: "邮件发送失败" }); } } catch (error) { console.error(error); res.status(500).json({ message: "服务器错误", error: error.message }); } }) app.post("/sendEmailFromWorkForm", async (req,res)=>{ const { emailAddress, emailSubject, emailBody } = req.body try { let result = await sendEmail(emailAddress, emailSubject, emailBody, "work") if(result) { res.status(200).json({ message: "邮件发送成功" }); } else { res.status(500).json({ message: "邮件发送失败" }); } } catch (error) { console.error(error); res.status(500).json({ message: "服务器错误", error: error.message }); } }) // 修复日志输出 app.listen(8080 , ()=>{ console.log('Server started on port 8080.') })
客户端优化代码
async function sendEmail(address, subject, body){ try { let response = await fetch('mysiteURL',{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ emailAddress: address, emailSubject: subject, emailBody: body }) }) if(response.ok){ const data = await response.json(); // 等待json解析完成 console.log('email sent okay:', data.message); } else{ const errorData = await response.json(); console.log('Email failed to send:', errorData.message); } } catch (error) { console.log('请求失败:', error.message); } }
内容的提问来源于stack exchange,提问作者cauzality
相关产品推荐
相关产品推荐

