在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
相关产品推荐
相关产品推荐

