【问题标题】:Node express async file readNode express 异步文件读取
【发布时间】:2026-02-06 12:35:01
【问题描述】:

我有下面的代码,它正在读取一个大小约为 600mb 的大型 XML 文件并返回以下输出:

Total Number of Lines in File: 12077214
Read in 3774.015ms

代码:

var express = require("express");
var router = express.Router();
var fs = require("fs");
var es = require("event-stream");
var now = require("performance-now");

router.get("/testapi", function (req, res, next) {
  var totalLines = 0;
  var t0 = now();
  var t1;

  fs.createReadStream("./large_xml_test.xml")
    .pipe(es.split())
    .pipe(
      es
        .mapSync(function (line) {
          if (line != "") {
            totalLines++;
          }
        })
        .on("error", function (err) {
          console.log("Error while reading file.", err);
        })
        .on("end", function () {
          console.log("Read entire file.");
          t1 = now();
          console.log("Total Number of lines: ", totalLines);

          console.log(
            `Performance now line count timing: ` + (t1 - t0).toFixed(3) + `ms`
          );
          res.send(
            `Total Number of Lines in File: ${totalLines.toString()}<br/>Read in ${(
              t1 - t0
            ).toFixed(3)}ms`
          );
        })
    );
});

module.exports = router;

我尝试使用 async 和 awaits 关键字将其移至单独的函数,但使用 res.send 时不显示结果,即调用 API 但立即返回 200 并且没有发生等待?

似乎控制台日志函数仅在 vs 代码中触发,但仅此而已,有没有办法将此函数移动到单独的文件并让 res.send 等待返回值?

感谢目前正在学习nodejs。

=================== 编辑===================

在回答 Keiths 的声明时,这是我测试但失败的最后一项:

文件 1:

var express = require("express");
var router = express.Router();
const test = require("../functions/readFileEventStream");

router.get("/testapi", function (req, res, next) {
  let message = "API is working properly";
  const results = (async function () {
    await test.readLargeFile();
  })();
  res.send(results);
});

module.exports = router;

文件 2

var fs = require("fs");
var es = require("event-stream");
var now = require("performance-now");

var totalLines = 0;
var t0 = now();
var t1;

async function readLargeFile() {
  fs.createReadStream("./large_xml_test.xml")
    .pipe(es.split())
    .pipe(
      es
        .mapSync(function (line) {
          if (line != "") {
            totalLines++;
          }
        })
        .on("error", function (err) {
          console.log("Error while reading file.", err);
        })
        .on("end", function () {
          t1 = now();
          var msg = `Total Number of Lines in File: ${totalLines.toString()},\r\nRead in ${(
            t1 - t0
          ).toFixed(3)}ms`;
          return msg;
        })
    );
}

module.exports = {
  readLargeFile,
};

【问题讨论】:

  • 是的,你可以把这个逻辑包装到一个 Promise 构造函数中,然后你可以在你的路由中等待它。如果您显示您尝试拆分的代码可能是最好的,然后我们可以解释如何修复。
  • @Keith 我已经添加了我尝试过的测试代码还有其他尝试但我覆盖了它们这是最后一个。
  • 查看 Edwards 的回答,它向您展示了如何将基于事件的回调转换为 Promise 构造函数,然后您可以稍后等待。

标签: javascript node.js express


【解决方案1】:

读取文件的逻辑可以封装成一个Promise。为什么是承诺? 承诺由 await 机制支持,因此它可以在异步函数中等待。通常在 Promise 中放置不同步的代码,因此将 mapSync 更改为 map,以便可以异步处理文件。

router.get("/testapi", async function (req, res) {
    try {
        let t0 = now();
        let totalLines = await readFile('./large_xml_test.xml');
        console.log("Read entire file.");
        let t1 = now();
        console.log("Total Number of lines: ", totalLines);

        console.log(
            `Performance now line count timing: ` + (t1 - t0).toFixed(3) + `ms`
        );
                        
        res.send(
            `Total Number of Lines in File: ${totalLines.toString()}<br/>Read in ${(
                t1 - t0
            ).toFixed(3)}ms`
        );
    }
    catch(err) {
        console.log('error reading file ' + err);
        res.status(500).send('failed to read file');
    }
});

function readFile(path) {
    return new Promise(function (resolve, reject) {
        let totalLines = 0;
        fs.createReadStream(path)
            .pipe(es.split())
            .pipe(
                es.map(function (line, cb) {
                    if (line != "") {
                        totalLines++;
                    }

                    cb(null);
                })
                    .on("error", function (err) {
                        reject(err);
                    })
                    .on("end", function () {
                        
                        resolve(totalLines);
                    })
            );
    })
}

【讨论】:

  • 啊,谢谢你,这实际上教会了我很多关于我显然误解的异步结构以及承诺系统是我所缺少的前进方向的事实;还要感谢您指出不推荐使用 mapSync 功能。非常感谢为此付出的时间和精力。
【解决方案2】:

这里要记住的是,您的 XML 阅读器和路由处理程序都是 事件驱动的函数。在您的示例中,您从 inside 调用 res.send() on("end"...) 事件处理程序。这就是为什么事情会按照你想要的方式等待。

拆分 XML 阅读器和路由处理程序的最简单方法是将它们都设为async。此代码为示例,未经调试。

路由处理程序

router.get("/testapi", async function (req, res, next) {

  var t0 = now();
  var t1;

  var totalLines = await getXML ("./large_xml_test.xml"); 

  console.log("Read entire file.");
  t1 = now();
  console.log( whatever );
  res.send( whatever );
} )

您的 xml 阅读器模块

var fs = require("fs");
var es = require("event-stream");

async function getXML (file)
  let totalLines = 0
  fs.createReadStream(file)
    .pipe(es.split())
    .pipe(
      es
        .mapSync(function (line) {
          if (line != "") {
            totalLines++;
          }
        })
        .on("error", function (err) {
          throw new Error ("Error while reading file.", err);
        })
        .on("end", function () {
           return totalLines;
        })
     );
});

module.exports = getXML;

诀窍是这样的。 async getXML 直到完成并点击它的 on("end"...) event handler. And so your route handler doesn't call res.send()` 后才会返回,直到您的 xml 加载操作完成。

【讨论】:

  • @EduardHasanaj 的解决方案也不错。在我的项目中,我使用了async 而不是明确的 Promise,但它们几乎是等效的解决方案。
  • 很遗憾那里不一样,这会失败。这只会返回一个返回 undefined 的 Promise。