基于Express.js与Fetch,如何让客户端获取后端已完成API拉取的正确数据
核心问题定位
- 当前实现的时序错位根源十分明确:一是把
sharpe设计为全局变量,和具体请求的上下文完全解绑;二是setup()内部如果没有把所有外部API的异步拉取流程全部await完成就提前执行后续逻辑,await setup()根本起不到等待数据就绪的作用,最终发出去的响应自然会带着空的全局变量值。 - 全局变量的设计本身还有并发隐患:多用户同时发起请求时,不同请求的计算结果会互相覆盖全局值,哪怕时序逻辑对了也容易拿到错值。
零数据库的最简优化方案
完全不需要引入数据库落库的流程,只要把全局变量的传递逻辑改成请求作用域内的独立传递,就能从根上解决空值问题:
改造setup()函数
摒弃原来“修改全局变量存结果”的写法,直接把最终计算好的夏普值作为函数返回值带出,保证所有外部API调用全部完成、数据计算准确后,再结束函数执行:
async function setup(stock1, stock2) { // 替换原来依赖全局thisOne、thisOne2的逻辑,直接用入参作为计算依据 // 所有外部API请求必须逐次await完成,拿到返回值后再做计算 const finalSharpe = '所有计算完成后的有效结果' // 所有异步流程走完后,再返回计算结果 return finalSharpe }
改造Express接口逻辑
删掉最外层声明的全局sharpe变量,直接用setup()返回的有效结果构造响应,严格保证只有拿到完整数据后才发送响应:
app.post('/api', async (req, res, next) => { console.log('I got a request!') const stock1 = req.body.stock1 const stock2 = req.body.stock2 // 把当前请求的入参传给计算函数,只有等计算全流程跑完,才会执行后续代码 const sharpeResult = await setup(stock1, stock2) res.json({ status: 'success', stocks: sharpeResult }) })
这种方案的优势是:响应内容完全绑定当前请求的执行链路,只要await setup()没有返回,Express就不会发送响应给客户端,从机制上杜绝了过早返回空值的可能;同时每个请求的计算过程完全隔离,不会出现多请求互相覆盖数据的问题。
短平快的过渡适配方案
如果暂时没法大改setup()内部的逻辑,至少要在响应前加一层有效性判断,避免把空值发给客户端:
app.post('/api', async (req, res, next) => { console.log('I got a request!') thisOne = req.body.stock1 thisOne2 = req.body.stock2 await setup(); // 数据未就绪时直接返回错误,不发送空响应 if (!sharpe) { return res.status(500).json({ status: 'error', message: '外部数据拉取未完成,请稍后重试' }) } res.json({ status: 'success', stocks: sharpe }); })
注:这个过渡方案只适合单用户本地调试场景,生产环境多并发时全局变量依然会出现值覆盖的问题,长期还是推荐使用请求作用域传值的方案。
客户端配套调整
现有客户端的fetch逻辑本身没有问题,不需要做轮询等待,只要加上基础的加载态提示即可,所有渲染逻辑全部等服务端响应返回后再执行:
const sendData = async (event) => { event.preventDefault(); var stock1 = document.getElementById('weight1').value var stock2 = document.getElementById('weight2').value const data = {stock1, stock2}; const options = { method: 'POST', body: JSON.stringify(data), headers: {'Content-Type': 'application/json' } } // 发起请求后立刻禁用提交按钮,给用户数据加载的提示 document.querySelector('button[type="submit"]').disabled = true fetch('/api', options) .then(res => res.json()) .then(res => { console.log(res.stocks); // 拿到服务端返回的有效值后,再执行页面内容渲染 }) .finally(() => { // 请求流程结束后恢复按钮状态 document.querySelector('button[type="submit"]').disabled = false }) }
内容的提问来源于stack exchange,提问作者Aftersun Lotion
相关产品推荐
相关产品推荐

