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

在Superagent管道传输前捕获请求错误的实现方案

解决Superagent管道传输时的错误处理问题

看起来你已经搞定了正常流程的文件管道传输,但错误场景的处理确实容易被忽略——毕竟Superagent的管道默认会把所有响应内容都转存,不管状态码对不对。我来帮你梳理下问题根源,然后给出两种可行的解决方案:

问题出在哪?

你当前的代码是先把服务A的所有响应(包括401、404这类错误响应)都写入本地文件,之后才通过streamToPromise返回路径。这就导致错误内容被存成了CSV文件,最后还返回给了Postman,完全不符合预期。而且streamToPromise只监听了文件流的finish和error,没处理请求层面的状态码异常。

方案一:保留本地文件存储,完善错误拦截

如果你的业务确实需要先把文件存到本地,那可以修改download_csv函数,在收到响应时先检查状态码,异常时立即终止请求、清理文件并抛出错误:

const fs = require('fs');
const path = require('path');
const agent = require('superagent'); // 假设你已引入superagent

router.post("/csv", (req, res) => {
  download_csv(req.get("Authorization"), req.body.ids)
    .then((filePath) => {
      res.sendFile(path.resolve(filePath));
      // 可选:返回文件后删除临时文件,避免占用磁盘空间
      fs.unlink(filePath, (err) => {
        if (err) console.error('清理临时文件失败:', err);
      });
    })
    .catch((err) => {
      res.status(err.status || 500).json(err.body || { message: err.message });
    });
});

function download_csv(token, ids) {
  return new Promise((resolve, reject) => {
    const tempFilePath = "filters.csv";
    const writeStream = fs.createWriteStream(tempFilePath);

    const request = agent
      .post(`${profiles}/api/documents/csv`)
      .set("authorization", token)
      .send({ ids: ids, action: DOWNLOAD_CSV_PROFILES });

    // 第一步:收到响应先检查状态码
    request.on("response", (serviceARes) => {
      if (serviceARes.status < 200 || serviceARes.status >= 300) {
        // 状态异常,终止请求
        request.abort();
        // 清理已创建的错误文件
        fs.unlink(tempFilePath, (cleanupErr) => {
          if (cleanupErr) console.error('清理错误文件失败:', cleanupErr);
        });
        // 抛出错误给Express的catch处理
        return reject({
          status: serviceARes.status,
          body: serviceARes.body,
          message: `请求失败,状态码:${serviceARes.status}`
        });
      }

      // 状态正常,开始管道传输
      request.pipe(writeStream);
    });

    // 处理请求层面的错误(比如网络超时、连接失败)
    request.on("error", (err) => {
      fs.unlink(tempFilePath, (cleanupErr) => {
        if (cleanupErr) console.error('错误时清理临时文件失败:', cleanupErr);
      });
      reject(err);
    });

    // 处理文件写入流的错误
    writeStream.on("error", (err) => {
      request.abort();
      fs.unlink(tempFilePath, (cleanupErr) => {
        if (cleanupErr) console.error('流错误时清理临时文件失败:', cleanupErr);
      });
      reject(err);
    });

    // 流写入完成,返回文件路径
    writeStream.on("finish", () => {
      resolve(tempFilePath);
    });
  });
}

关键改进点:

  • 把download_csv改成直接返回Promise,更灵活控制错误流程
  • 收到响应第一时间检查状态码,非2xx就终止请求并清理文件
  • 分别监听请求错误、流写入错误,确保任何异常都能终止流程并清理资源

方案二:直接管道到客户端,跳过本地文件

如果不需要本地存储文件,直接把服务A的响应管道到Express的res是更高效的方案,还能避免文件处理的麻烦:

router.post("/csv", (req, res) => {
  const token = req.get("Authorization");
  const ids = req.body.ids;

  const request = agent
    .post(`${profiles}/api/documents/csv`)
    .set("authorization", token)
    .send({ ids: ids, action: DOWNLOAD_CSV_PROFILES });

  request.on("response", (serviceARes) => {
    if (serviceARes.status < 200 || serviceARes.status >= 300) {
      // 直接把错误响应转发给客户端
      res.status(serviceARes.status).json(serviceARes.body);
      request.abort();
      return;
    }
    // 设置CSV文件的响应头,让客户端识别为下载文件
    res.setHeader("Content-Type", "text/csv");
    res.setHeader("Content-Disposition", `attachment; filename="filters.csv"`);
    // 直接把服务A的响应管道到客户端
    request.pipe(res);
  });

  // 处理网络类错误
  request.on("error", (err) => {
    res.status(500).json({ message: "获取CSV文件失败", error: err.message });
  });
});

这个方案去掉了本地文件的环节,错误处理更直接:状态异常时直接把服务A的错误响应返回给客户端,同时终止请求。

测试验证

你可以伪造令牌或者用错误的ID测试:

  • 触发401时,请求会被立即终止,不会生成错误内容的文件,Express会返回正确的401响应
  • 触发404时,同样会终止流程,返回404错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:39:06