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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 23:47:37