Express JS异步任务等待时渲染加载页的实现问题
问题根源
你当前的实现逻辑错误,因为HTTP是单次请求-响应模型,一次请求只能返回一次响应。你调用两次res.render时,第一次已经把响应发送给浏览器了,第二次调用会直接抛出错误(提示响应已发送),所以用户永远看不到加载完成后的页面。
可行解决方案
最直接的方案是:先渲染带加载动画的页面,然后由前端发起AJAX请求获取数据,拿到数据后再替换页面内容。
步骤1:拆分路由与控制器
修改dataControler.js,拆分出两个接口:一个返回加载页面,一个返回数据:
const asyncHandler = require("express-async-handler"); // 返回带加载动画的页面 exports.loadPage = (req, res) => { res.render("index", { title: "Loading data", loading: true, }); }; // 异步获取数据的接口 exports.getData = asyncHandler(async (req, res) => { const data = await do_long_dataSyncing(); res.json({ title: "Data is cooked!", data: data, }); }); // 修正数据请求函数(原函数没有返回Promise) const do_long_dataSyncing = async () => { const response = await fetch('someEndpoint'); if (!response.ok) throw new Error('Failed to fetch data'); return response.json(); };
修改index.js,添加对应的路由:
const express = require("express") const router = express.Router() const data_controller = require("../controllers/dataControler") router.get("/", data_controller.loadPage) router.get("/api/data", data_controller.getData) module.exports = router
步骤2:修改PUG模板,添加前端异步逻辑
在index.pug的loading区块中添加脚本,发起AJAX请求获取数据,拿到数据后替换页面内容:
doctype html html(lang='en') head if title title= title else title Loading data! meta(charset='utf-8') meta(name='viewport' content='width=device-width, initial-scale=1') link(rel='stylesheet' href='/bootstrap/bootstrap.css') body if loading div(class="text-center") div(class="spinner-border m-5" role="status") span(class='visually-hidden') Loading... // 添加异步请求脚本 script. window.addEventListener('DOMContentLoaded', async () => { try { const response = await fetch('/api/data'); const result = await response.json(); // 替换页面标题 document.title = result.title; // 替换body内容为数据展示区块 document.body.innerHTML = ` <div class='container-fluid'> <div class='row'> <div class='col-sm-2'> <!-- 这里可以插入侧边栏内容 --> </div> <div class='col-sm-10'> <!-- 渲染数据,示例:展示data的JSON结构 --> <pre>${JSON.stringify(result.data, null, 2)}</pre> </div> </div> </div> `; } catch (err) { document.body.innerHTML = `<div class='text-center mt-5'>加载失败:${err.message}</div>`; } }); else div(class='container-fluid') div(class='row') div(class='col-sm-2') block sidebar div(class='col-sm-10') block content
可选方案:服务器发送事件(SSE)
如果需要服务器主动推送数据(比如数据处理过程有进度更新),可以使用SSE:
- 控制器先返回加载页面,同时保持SSE连接。
- 数据处理完成后,通过SSE将数据推送给前端,前端再更新页面。
这种方案相对复杂,适合需要实时反馈进度的场景,普通数据请求用第一种方案足够。
内容的提问来源于stack exchange,提问作者Marco Blanchette
相关产品推荐
相关产品推荐

