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

如何在TypeScript中从FTP服务器批量获取XML文件?

问题描述

我尝试使用ftp Node.js模块从FTP服务器加载一批XML文件,但它不符合我的需求——该模块未实现async/await,难以整合到可读性良好的代码中。有没有更优的实现方式?

以下是我当前的代码(可运行,但异步逻辑混乱):

import Client from 'ftp';
import ReadableStream = NodeJS.ReadableStream;

const c = new Client();

function streamToString(stream: ReadableStream) {
    // lets have a ReadableStream as a stream variable
    const chunks = [];

    for await (const chunk of stream) {
        chunks.push(Buffer.from(chunk));
    }

    return Buffer.concat(chunks).toString("utf-8");
}
const getFile = async () => {
    let fname: string = ""

    c.on('ready', () => {
        c.list(".", async (err, list) => {
            if (err) throw err;
            // console.dir(list);
            for (const fileProp of list) {
                console.log("fileProp: " , './' +fileProp.name)
                fname = fileProp.name

                if(fileProp.name.match(/.*\.xml/)){

                     c.get('./' + fileProp.name,  (err, stream) => {
                        if (err) throw err;
                        console.log("fname: " + fname)
                        const result =  streamToString(stream)

                        console.log("file content:\n" + result)
                        return
                    });
                }
            }

            c.end();
        });
    });
    c.on('error', () => {
        console.log('handle error');
    });

    await c.connect({
        host: "myserver.com",
        user: "myuser",
        password: "mypassword",
        //debug: m => console.log(m)
    })

}
更优实现方式

方案一:使用原生支持async/await的FTP库

优先推荐使用basic-ftp,这个库原生适配Promise,能直接用async/await编写线性化的异步代码,大幅提升可读性。

安装依赖

npm install basic-ftp

示例代码

import { Client } from "basic-ftp";
import ReadableStream = NodeJS.ReadableStream;

async function streamToString(stream: ReadableStream): Promise<string> {
    const chunks: Buffer[] = [];
    for await (const chunk of stream) {
        chunks.push(Buffer.from(chunk));
    }
    return Buffer.concat(chunks).toString("utf-8");
}

async function getXmlFiles() {
    const client = new Client();
    try {
        // 建立FTP连接
        await client.access({
            host: "myserver.com",
            user: "myuser",
            password: "mypassword",
            // debug: console.log // 可选,开启调试日志
        });

        // 获取当前目录文件列表
        const list = await client.list();
        for (const fileProp of list) {
            // 筛选XML文件
            if (fileProp.name.match(/.*\.xml/)) {
                console.log("读取文件:", fileProp.name);
                // 下载文件流并转换为字符串
                const stream = await client.downloadToStream(fileProp.name);
                const content = await streamToString(stream);
                console.log("文件内容:\n", content);
            }
        }
    } catch (err) {
        console.error("操作失败:", err);
    } finally {
        // 确保关闭连接
        client.close();
    }
}

// 执行函数
getXmlFiles();

方案二:封装现有ftp模块为Promise版本

如果不想更换依赖,可以手动把原ftp模块的回调方法封装成Promise,实现async/await支持:

封装后的代码

import Client from 'ftp';
import ReadableStream = NodeJS.ReadableStream;

// 封装ready事件为Promise
function waitForReady(client: Client): Promise<void> {
    return new Promise(resolve => client.once('ready', resolve));
}

// 封装list方法为Promise
function listFiles(client: Client, path: string): Promise<Client.ListingElement[]> {
    return new Promise((resolve, reject) => {
        client.list(path, (err, list) => err ? reject(err) : resolve(list!));
    });
}

// 封装get方法为Promise
function getFileStream(client: Client, path: string): Promise<ReadableStream> {
    return new Promise((resolve, reject) => {
        client.get(path, (err, stream) => err ? reject(err) : resolve(stream!));
    });
}

async function streamToString(stream: ReadableStream): Promise<string> {
    const chunks: Buffer[] = [];
    for await (const chunk of stream) {
        chunks.push(Buffer.from(chunk));
    }
    return Buffer.concat(chunks).toString("utf-8");
}

async function getXmlFiles() {
    const client = new Client();
    try {
        client.on('error', err => console.error("连接出错:", err));
        
        // 发起连接
        await client.connect({
            host: "myserver.com",
            user: "myuser",
            password: "mypassword",
            // debug: console.log
        });
        
        // 等待连接就绪
        await waitForReady(client);
        
        // 获取文件列表
        const list = await listFiles(client, ".");
        for (const fileProp of list) {
            if (fileProp.name.match(/.*\.xml/)) {
                console.log("读取文件:", fileProp.name);
                const stream = await getFileStream(client, fileProp.name);
                const content = await streamToString(stream);
                console.log("文件内容:\n", content);
            }
        }
    } catch (err) {
        console.error("操作失败:", err);
    } finally {
        client.end();
    }
}

// 执行函数
getXmlFiles();

说明

  • 方案一无需自行封装,代码更简洁易维护,是首选方案。
  • 方案二适合需要保留原依赖的场景,通过Promise封装让异步逻辑线性化,解决回调嵌套导致的可读性问题。

内容的提问来源于stack exchange,提问作者Gerd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 01:20:19