Node.js将文件下载到指定目录时触发ENOENT错误求助
问题:文件写入触发ENOENT错误,目录已存在但无法创建文件
我开发艺术站点归档工具时,使用fs.createWriteStream(this.file_path, {flags: "w"})下载图片到指定目录,触发如下错误:
Error: ENOENT: no such file or directory, open 'E:\my_project_root\downloaded\artists\artist_name\file_name.jpg'
已知情况:
- 目标目录(如
my_project_root\downloaded\artists\artist_name\)可正常创建,删除后脚本运行时也能重新生成 - 另一台电脑运行相同代码正常,仅项目所在路径不同,怀疑是副硬盘权限问题
核心代码
my_project_root/lib/data/downloader.ts
import * as https from "https"; import * as fs from "fs"; import axios from "axios"; import * as path from "path"; export class Downloader { private dirname: string = ""; private artist: string = ""; private dir_to_check: string = path.join(".", "downloaded", "artists"); private file_path: string = path.join(".", "downloaded", "artists"); private url: string = ""; public constructor(dirname: string, artist: string, fileNameWithExt: string, url: string) { this.dirname = dirname; this.artist = artist; this.dir_to_check = path.join(this.dir_to_check, artist); this.file_path = path.join(dirname, this.file_path, artist, fileNameWithExt); this.url = url; } public async downloadImage(): Promise<boolean> { console.log(` __dirname: ${this.dirname} dir_to_create_if_not_exist: ${this.dir_to_check} file_path: ${this.file_path} artist: ${this.artist} `); const dirCreated = await this.dirCreateIfNotExists(); if (!dirCreated) { return false; } try { const writer: any = fs.createWriteStream(this.file_path, {flags: "w"}); writer.on("open", () => { console.log(`File opened for writing: ${this.file_path}`); }); const writeStreamPromise = new Promise<boolean>((resolve, reject) => { writer.on("finish", () => { console.log(`Write stream finished for: ${this.file_path}`); resolve(true); }); writer.on("error", (error) => { console.error(`Error during write: ${error}`); resolve(false); }); }); const response = await axios({ method: "GET", url: this.url, responseType: "stream", httpsAgent: new https.Agent({ rejectUnauthorized: false }), headers: { "User-Agent": "my_user_agent_here", "Authorization": "Basic " + btoa(`${process.env.key}:${process.env.username}`), }, }); response.data.pipe(writer); console.log(response.status); return new Promise((resolve, reject) => { writer.on("finish", () => { resolve(true); }); writer.on("error", (error) => { console.error("Error during download:", error); resolve(false); }); }); } catch (error) { console.error("downloadImage() error: " + error); return false; } } private async dirCreateIfNotExists(): Promise<boolean> { const targetDir: string = path.join(this.dir_to_check); try { await fs.promises.mkdir(targetDir, {recursive: true}); return true; } catch (error) { console.error("Error creating directory:", error); return false; } } }
my_project_root/lib/process/download.ts(核心片段)
this.contents_of_files.forEach(async contents => { contents = JSON.parse(contents); let data: any = contents[0]; this.id = data.id; this.createdAt = data.created_at; this.updatedAt = data.updated_at; this.url = data.file.url; this.fileExt = data.file.ext; this.md5 = data.file.md5; this.artists = data.tags.artist; await this.initiateDownload(dirname); }); async initiateDownload(dirname) { for (const artist of this.artists) { const file: string =`${this.md5}__${this.id}__${this.createdAt}__${this.updatedAt}.${this.fileExt}`; this.Downloader = new Downloader(dirname, artist, file, this.url); await this.Downloader.downloadImage(); } }
解决方案
1. 修复路径拼接错误
构造函数中file_path的拼接逻辑存在重复路径问题,导致实际路径可能不符合预期。修改路径初始化逻辑:
// 调整Downloader类的构造函数 public constructor(dirname: string, artist: string, fileNameWithExt: string, url: string) { this.dirname = dirname; this.artist = artist; // 直接基于项目根目录拼接目标目录 this.dir_to_check = path.join(dirname, "downloaded", "artists", artist); // 基于目标目录拼接文件路径,避免重复嵌套 this.file_path = path.join(this.dir_to_check, fileNameWithExt); this.url = url; }
2. 强化目录存在性验证
在创建写入流前,额外验证目录是否存在,确保mkdir递归创建生效:
// 在downloadImage方法中,创建writeStream前添加 const dirExists = await fs.promises.access(this.dir_to_check, fs.constants.F_OK) .then(() => true) .catch(() => false); if (!dirExists) { console.error(`目录不存在:${this.dir_to_check}`); return false; }
3. 解决副硬盘权限问题
如果项目位于副硬盘,执行以下操作:
- 右键项目根目录 → 属性 → 安全 → 确认当前用户拥有写入权限,必要时添加「完全控制」权限
- 以管理员身份运行Node.js脚本,绕过权限限制
- 检查副硬盘是否设置为只读模式,在「磁盘管理」中修改磁盘属性
4. 合并重复的Promise监听
当前代码中创建了两个监听流状态的Promise,会导致逻辑混乱,合并为一个:
// 替换downloadImage方法中的Promise逻辑 try { const writer = fs.createWriteStream(this.file_path, { flags: "w" }); writer.on("open", () => { console.log(`File opened for writing: ${this.file_path}`); }); const response = await axios({ method: "GET", url: this.url, responseType: "stream", httpsAgent: new https.Agent({ rejectUnauthorized: false }), headers: { "User-Agent": "my_user_agent_here", "Authorization": "Basic " + btoa(`${process.env.key}:${process.env.username}`), }, }); response.data.pipe(writer); console.log(response.status); // 合并为单个Promise处理结果 return new Promise<boolean>((resolve) => { writer.on("finish", () => { console.log(`Write stream finished for: ${this.file_path}`); resolve(true); }); writer.on("error", (error) => { console.error("Error during download/write:", error); resolve(false); }); }); } catch (error) { console.error("downloadImage() error: " + error); return false; }
5. 修复forEach异步陷阱
forEach中的async回调不会等待异步操作完成,可能导致并发冲突,替换为for...of循环:
// 替换download.ts中的forEach循环 for (const contents of this.contents_of_files) { const parsedContents = JSON.parse(contents); let data = parsedContents[0]; this.id = data.id; this.createdAt = data.created_at; this.updatedAt = data.updated_at; this.url = data.file.url; this.fileExt = data.file.ext; this.md5 = data.file.md5; this.artists = data.tags.artist; await this.initiateDownload(dirname); }
内容的提问来源于stack exchange,提问作者quietWind01
相关产品推荐
相关产品推荐

