如何在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
相关产品推荐
相关产品推荐

