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

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:

  1. 控制器先返回加载页面,同时保持SSE连接。
  2. 数据处理完成后,通过SSE将数据推送给前端,前端再更新页面。
    这种方案相对复杂,适合需要实时反馈进度的场景,普通数据请求用第一种方案足够。

内容的提问来源于stack exchange,提问作者Marco Blanchette

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:55:37