SSH2 SFTP未捕获异常:无法启动子系统,如何完善错误捕获
问题描述
现有一段从S3向SFTP服务器传输文件的代码,整体功能正常,但遇到网络连接类错误(如Error: Unable to start subsystem: sftp)时无法被捕获。尝试过.on()和try...catch处理仍未解决,需要完善错误捕获逻辑。
原代码如下:
import { Client, SFTPWrapper } from "ssh2"; import { S3 } from "@aws-sdk/client-s3"; const s3Client = new S3({}); await new Promise(async (resolve, reject) => { const file = await s3Client.getObject({ Bucket: sourceBucket, Key: sourceKey }); const readStream = new Readable(); readStream.push(await file.Body!.transformToByteArray()); readStream.push(null); const destinationFilePath = `${path}${sourceKey.split("/").pop()}`; const client = new Client(); client .on("ready", () => { client.sftp((err: Error | undefined, sftp: SFTPWrapper) => { if (err) reject (err); const writeStream = sftp?.createWriteStream(destinationFilePath); readStream?.pipe(writeStream); writeStream.on("close", () => { sftp.end(); client.end(); resolve("Success"); }).on("error", (err: string | undefined) => { reject (err); }); }); }) .connect({ host, port, username: userId, password: SecretString }) .on("error", (err) => { reject (err); }); });
错误捕获完善方案
问题根源
- Promise executor内部异步操作未处理:外层Promise的executor是async函数,内部
await s3Client.getObject()的错误没有被捕获,一旦S3请求失败会直接抛出未处理异常。 - 事件绑定顺序错误:
connect()之后才绑定error事件,可能导致连接初期的错误无法被监听。 - SFTP子系统错误未做资源清理:
client.sftp()回调中遇到错误时,仅调用reject但未关闭客户端连接,会造成资源泄漏。 - 可选链隐含风险:
sftp?.createWriteStream和readStream?.pipe的可选链会掩盖潜在的undefined调用问题,同时相关错误无法被捕获。
修复后的代码
import { Client, SFTPWrapper } from "ssh2"; import { S3 } from "@aws-sdk/client-s3"; const s3Client = new S3({}); // 外层统一捕获所有异步错误 try { // 单独处理S3文件读取,提前捕获错误 const file = await s3Client.getObject({ Bucket: sourceBucket, Key: sourceKey }); const readStream = new Readable(); try { const fileBytes = await file.Body!.transformToByteArray(); readStream.push(fileBytes); readStream.push(null); } catch (s3Err) { throw new Error(`S3文件读取失败: ${s3Err.message}`); } await new Promise((resolve, reject) => { const client = new Client(); // 先绑定error事件再调用connect,确保所有连接阶段错误被监听 client .on("error", (connErr) => { reject(new Error(`SFTP连接错误: ${connErr.message}`)); }) .on("ready", () => { client.sftp((sftpErr: Error | undefined, sftp: SFTPWrapper) => { if (sftpErr) { client.end(); // 出错后立即关闭客户端 return reject(new Error(`SFTP子系统启动失败: ${sftpErr.message}`)); } const destinationFilePath = `${path}${sourceKey.split("/").pop()}`; const writeStream = sftp.createWriteStream(destinationFilePath); // 监听读取流错误,避免流异常未捕获 readStream.on("error", (streamErr) => { writeStream.destroy(); sftp.end(); client.end(); reject(new Error(`S3读取流错误: ${streamErr.message}`)); }); readStream.pipe(writeStream); writeStream.on("close", () => { sftp.end(); client.end(); resolve("Success"); }).on("error", (writeErr) => { sftp.end(); client.end(); reject(new Error(`SFTP写入流错误: ${writeErr.message}`)); }); }); }) .connect({ host, port, username: userId, password: SecretString }); }); } catch (err) { console.error("文件传输失败:", err); throw err; // 按需决定是否向上层抛出错误 }
关键优化点
- 调整事件绑定顺序:先绑定
error事件再调用connect,确保连接初始化阶段的所有错误都能被捕获。 - 拆分异步逻辑:将S3文件读取移到外层Promise之外,单独用
try...catch处理,避免Promise executor内部的未处理异常。 - 完善错误时的资源清理:任何错误发生时,立即销毁流、关闭SFTP连接和客户端,防止资源泄漏。
- 明确错误上下文:给每个错误添加场景描述,方便快速定位问题。
- 移除不必要的可选链:在确认
sftp和readStream有效的前提下,直接调用相关方法,避免隐含的undefined错误。
内容的提问来源于stack exchange,提问作者navig8tr
相关产品推荐
相关产品推荐

