使用createReadStream、createWriteStream及yauzl迁移压缩图片报错排查
问题排查与yauzl正确实现指导
问题概述
开发任务:用户输入目录名和图片名后,将指定目录中的压缩图片通过流结合yauzl包迁移至temp目录。当前代码运行触发EISDIR错误,相关信息如下:
原代码
import fs, { createReadStream } from "fs"; import inquirer from "inquirer"; import terminalKit from "terminal-kit"; import yauzl from "yauzl"; inquirer .prompt([ { type: "input", name: "catalog_name", message: "Enter catalog name:", }, { type: "input", name: "image_name", message: "Enter image name:", }, ]) .then((answers) => { const catalogName = answers.catalog_name; const imageName = answers.image_name; const filePath = `./${catalogName}/${imageName}`; if (!fs.existsSync(`./${catalogName}`)) { throw new Error("Catalog does not exist!"); } if (fs.existsSync(catalogName)) { fs.readdir(catalogName, (err, files) => { const fileDirectoryArr = files; if (fileDirectoryArr.includes(imageName)) { const readStream = createReadStream("./images"); const writeStream = fs.createWriteStream("./temp"); readStream.pipe(writeStream); } else { throw new Error("Image does not exist!"); } }); } }) .catch((error) => { console.log("Something went wrong!", error); });
错误信息
node:events:491 throw er; // Unhandled 'error' event ^ Error: EISDIR: illegal operation on a directory, open 'C:\Users\jkuki\Desktop\ONL_FSF_E_16_Egzamin_1\01_Strumienie\temp' Emitted 'error' event on WriteStream instance at: at WriteStream.onerror (node:internal/streams/readable:773:14) at WriteStream.emit (node:events:513:28) at emitErrorNT (node:internal/streams/destroy:157:8) at emitErrorCloseNT (node:internal/streams/destroy:122:3) at processTicksAndRejections (node:internal/process/task_queues:83:21) { errno: -4068, code: 'EISDIR', syscall: 'open', path: 'C:\\Users\\jkuki\\Desktop\\ONL_FSF_E_16_Egzamin_1\\01_Strumienie\\temp' }
目录结构
根目录下存在images、temp两个目录,images目录内存放压缩格式的图片文件。
错误原因分析
- 流操作目标错误:
createReadStream("./images")指向目录而非具体压缩文件,createWriteStream("./temp")同样指向目录,Node.js流无法直接对目录进行读写,这是触发EISDIR错误的直接原因。 - 未使用yauzl核心功能:代码导入了yauzl包但未调用,完全没实现读取压缩文件的逻辑。
- 异步错误无法捕获:
fs.readdir回调中抛出的错误无法被外层.catch捕获,会导致未处理异常。 - 逻辑冗余:重复判断目录是否存在,多余且无意义。
修复方案与正确实现
关键修复点
- 确保读写流指向具体文件而非目录
- 基于yauzl实现压缩文件的读取与图片提取逻辑
- 用Promise封装异步操作,统一错误捕获
- 提前创建
temp目录(若不存在)
正确代码
import fs from "fs/promises"; import { createWriteStream } from "fs"; import inquirer from "inquirer"; import yauzl from "yauzl"; // 确保temp目录存在 const ensureTempDir = async () => { try { await fs.access("./temp"); } catch { await fs.mkdir("./temp"); } }; inquirer .prompt([ { type: "input", name: "catalog_name", message: "输入目录名:", }, { type: "input", name: "image_name", message: "输入图片名:", }, ]) .then(async (answers) => { const catalogName = answers.catalog_name; const imageName = answers.image_name; const zipFilePath = `./${catalogName}/${imageName}`; // 检查目录是否存在 try { await fs.access(`./${catalogName}`); } catch { throw new Error("目录不存在!"); } // 检查压缩文件是否存在 try { await fs.access(zipFilePath); } catch { throw new Error("指定的压缩图片文件不存在!"); } await ensureTempDir(); // 使用yauzl读取zip文件并提取目标图片 return new Promise((resolve, reject) => { yauzl.open(zipFilePath, { lazyEntries: true }, (err, zipfile) => { if (err) return reject(err); zipfile.readEntry(); zipfile.on("entry", (entry) => { // 匹配压缩包内的目标图片(可根据实际文件名规则调整匹配逻辑) const targetImageName = imageName.replace(/\.zip$/, ""); if (entry.fileName === targetImageName) { zipfile.openReadStream(entry, (err, readStream) => { if (err) return reject(err); const writeStream = createWriteStream(`./temp/${entry.fileName}`); readStream.pipe(writeStream); writeStream.on("finish", () => { console.log(`图片已成功迁移至temp目录`); zipfile.close(); resolve(); }); writeStream.on("error", (err) => reject(err)); }); } else { zipfile.readEntry(); // 跳过无关文件 } }); zipfile.on("error", (err) => reject(err)); zipfile.on("close", () => resolve()); }); }); }) .catch((error) => { console.error("出错了!", error.message); });
代码说明
ensureTempDir函数:异步检查并创建temp目录,避免写入时因目录不存在报错。- Promise化异步操作:使用
fs/promises替代回调式方法,同时将yauzl的操作封装为Promise,确保所有错误能被外层.catch捕获。 - yauzl核心逻辑:
- 以
lazyEntries模式打开压缩文件,按需读取内部条目 - 遍历压缩包内的文件,匹配到目标图片后开启读取流
- 将读取流管道到
temp目录的写入流,完成后关闭zip文件
- 以
- 错误处理:统一捕获所有异步操作的错误,输出清晰的错误信息。
内容的提问来源于stack exchange,提问作者JKukielski
相关产品推荐
相关产品推荐

