fsx 简介:适用于 JavaScript 的现代文件系统 API

JavaScript 运行时中的文件系统 API 已经很久没有这么好了,这是我试图做出一个更好的文件系统 API 的尝试。

我们今天拥有的 JavaScript API 比十年前要好得多。考虑一下从 XMLHttpRequestfetch() 的转变:开发者体验显著改善,允许我们编写更简洁、功能性更强的代码来完成同样的事情。异步编程的 promises 的引入允许了这种变化,以及一系列其他变化,使得 JavaScript 更容易编写。然而,有一个领域几乎没有创新:服务器端 JavaScript 运行时的文件系统 API。

Node.js: 当今文件系统 API 的起源

Node.js 最初发布于 2009 年,随之诞生了 fs 模块。 fs 模块是围绕 Linux 的核心实用程序构建的,其中的许多方法都反映了它们的 Linux 灵感,如 rmdirmkdirstat 。为此,Node.js 成功创建了一个低级文件系统 API,可以处理开发人员希望在命令行上完成的任何事情。不幸的是,这就是创新的终点。

Node.js 文件系统 API 最大的改变是引入了 fs/promises ,将整个实用程序从基于回调的方法移动到基于 promise 的方法。较小的增量变化包括实现 web 流和确保 reader 也实现了异步迭代器。该 API 仍然使用专有的 Buffer 类来读取二进制数据。(尽管 Buffer 现在是 Uint8Array 的子类,但仍然存在不兼容性,这使得使用 Buffers 有问题。)

即使是 Ryan Dhal 在 Node.js 上的继任者 Deno,也没有在文件系统 API 上做太多的改进,它基本上遵循了与 Node.js 中的 fs 模块相同的模式,尽管它使用了 Uint8Arrays,而 Node.js 使用了 Buffer s,并且在不同的地方使用了异步迭代器,但它仍然采用了与 Node.js 相同的低级 API 方法。

只有 Bun,作为服务器端 JavaScript 运行时生态系统的最新成员,甚至尝试使用 Bun.file() 来更新文件系统 API,这是受 fetch() 的启发。虽然我赞赏这种对如何使用文件的重新思考,但当你处理多个文件时,为每个想要处理的文件创建一个新对象可能会很麻烦(当处理数千个文件时,会有一个巨大的性能损失)。除此之外,Bun 希望你使用 Node.js fs 模块进行其他操作。

一个现代的文件系统 API 会是什么样子?

在花费数年时间在维护 ESLint 的同时与 Node.js fs 模块斗争之后,我问自己,一个现代的文件系统 API 会是什么样子?

  • 通常情况下会很简单。至少 80%的时间,我不是读取文件就是写入文件,或者检查文件是否存在,差不多就是这样,然而这些操作充满了危险,因为我需要检查各种东西以避免错误或记住额外的属性(例如 { encoding: "utf8" } )。
  • 错误将很少发生。我对 fs 模块最大的抱怨就是它抛出错误的频率。在不存在的文件上调用 fs.stat() 会抛出错误,这意味着你实际上需要将每个调用包装在 try-catch 中。为什么?对于大多数应用程序来说,缺少文件并不是不可恢复的错误。
  • 行动将是可观察的。在测试文件系统操作时,我真的只是想要一种方法来验证我期望发生的事情是否确实发生了。我不想与其他一些实用程序建立间谍网络,这些实用程序可能会也可能不会改变我正在观察的方法的实际行为。
  • 模拟很容易。我总是惊讶于模拟文件系统操作的难度。最后我只能使用 proxyquire 之类的东西,否则就需要设置迷宫般的模拟,花上一段时间才能弄好。对于文件系统操作来说,这是一个很常见的需求,竟然还没有解决方案。

带着这些想法,我开始设计 fsx。

FSX 基础知识

fsx 库是我围绕现代高级文件系统 API 应该是什么样子的想法的结晶。 在这一点上,它专注于支持最常见的文件系统操作,而把较少使用的操作(例如 chmod )抛在后面。 (我并不是说这些操作在将来不会被添加,但对我来说,从最常见的情况开始,然后以与初始方法相同的谨慎方式构建更多的功能是很重要的。)

使用 fsx 运行时包

首先,fsx API 在三个运行时包中可用。这些包都包含相同的功能,但绑定到不同的底层 API。这些包是:

  • fsx-node - Node.js 中 fsx API 的绑定
  • fsx-deno - fsx API 的 Deno 绑定
  • fsx-memory - 适用于任何运行时(包括 web 浏览器)的内存实现

所以,开始时,你需要使用最适合你用例的运行时包。 为了本文的目的,我将专注于 fsx-node ,但相同的 API 存在于所有运行时包中. 所有运行时包都导出一个 fsx 单例,你可以以类似于 fs 的方式使用它。

import { fsx } from "fsx-node";

使用 fsx 读取文件

文件是通过使用返回特定数据类型的方法来读取的:

  • fsx.text(filePath) 读取给定的文件并返回一个字符串。
  • fsx.json(filePath) 读取给定的文件并返回一个 JSON 值。
  • fsx.arrayBuffer(filePath) 读取给定的文件并返回一个 ArrayBuffer

这里有一些例子:

// read plain text
const text = await fsx.text("/path/to/file.txt");// read JSON
const json = await fsx.json("/path/to/file.json");// read bytes
const bytes = await fsx.arrayBuffer("/path/to/file.png");

如果文件不存在,每个方法都会返回 undefined 而不是抛出错误。这意味着您可以使用 if 语句而不是 try-catch,并且可以选择使用 nullish 合并运算符来指定默认值,如下所示:

// read plain text
const text = (await fsx.text("/path/to/file.txt")) ?? "default value";// read JSON
const json = (await fsx.json("/path/to/file.json")) ?? {};// read bytes
const bytes =(await fsx.arrayBuffer("/path/to/file.png")) ?? new ArrayBuffer(16);

我觉得这种方法在 2024 年比不断担心不存在的文件出错更有 JavaScript 风格。

使用 fsx 写文件

要写文件,调用 fsx.write() 方法。这个方法接受两个参数:

  • filePath:string - 写入的路径
  • value:string|ArrayBuffer - 写入文件的值

这里有一个例子:

// write a string
await fsx.write("/path/to/file.txt", "Hello world!");const bytes = new TextEncoder().encode("Hello world!").buffer;// write a buffer
await fsx.write("/path/to/file.txt", buffer);

作为额外的好处,fsx.write() 将自动创建任何尚不存在的目录。这是我经常遇到的另一个问题,我认为它应该在现代文件系统 API 中“正常工作”。

使用 fsx 检测文件

要确定一个文件是否存在,使用 fsx.isFile(filePath) 方法,如果给定的文件存在,则返回 true ,否则返回 false

if (await fsx.isFile("/path/to/file.txt")) {// handle the file
}

fs.stat() 不同,如果文件不存在,这个方法会返回 false ,而不是抛出错误。

try {const stat = await fs.stat(filePath);return stat.isFile();
} catch (ex) {if (ex.code === "ENOENT") {return false;}throw ex;
}

删除文件和目录

fsx.delete() 方法接受一个参数,即要删除的路径,并且对文件和目录都有效。

// delete a file
await fsx.delete("/path/to/file.txt");// delete a directory
await fsx.delete("/path/to");

fsx.delete() 方法故意过于激进:它会递归地删除目录,即使它们不是空的(实际上是 rmdir -r )。

fsx 日志

fsx 的一个关键特性是,由于其内置的日志系统,很容易确定哪些方法被调用,并使用了哪些参数。要启用 fsx 实例的日志记录,请调用 logStart() 方法并传入一个日志名称。当你完成日志记录时,请调用 logEnd() 并传入相同的名称来检索日志条目的数组。

fsx.logStart("test1");const fileFound = await fsx.isFile("/path/to/file.txt");const logs = fsx.logEnd("test1");

每个日志条目都是一个包含以下属性的对象:

  • timestamp - 创建日志的数字时间戳
  • type - 描述日志类型的字符串
  • data - 与日志相关的附加数据

对于方法调用,日志条目的 typecall ,而 data 属性是一个对象,包含:

  • methodName - 被调用的方法的名称
  • args - 传递给方法的参数数组。

对于前面的例子, logs 将包含一个条目:

// example log entry{timestamp: 123456789,type: "call",data: {methodName: "isFile",args: ["/path/to/file.txt"]}
}

了解这一点后,您可以轻松地在测试中设置日志记录,然后检查调用了哪些方法,而无需使用第三方间谍库。

使用 fsx impls

fsx 的设计是这样的,抽象的核心功能包含在 fsx-core 包中,每个运行时包都扩展了该功能,使用特定于运行时的文件系统操作实现,这些操作被包装在一个称为 impl 的对象中。

  1. fsx 单例
  2. 一个构造函数,可以创建 fsx 的另一个实例(比如 fsx-node 中的 NodeFsx )
  3. 一个构造函数,可以创建运行时包的 impl 实例(如 node-fsx 中的 NodeFsxImpl )。

这可以让您只使用所需的功能。

fsx 中的 base impls 和 active impls

每个 fsx 实例都有一个 base 类实现,它定义了 fsx 对象在生产环境中的行为。active impls 是在任何给定时间使用的实现,它可能也是 base 类实现,也可能不是。你可以调用 fsx.setImpl() 来改变 active impls。

import { fsx } from "fsx-node";fsx.setImpl({json() {throw Error("This operation is not supported");},
});// somewhere elseawait fsx.json("/path/to/file.json"); // throws error

在此示例中,基本实现被替换为自定义实现,该自定义实现在调用 fsx.json() 方法时会引发错误。这使得您可以轻松地模拟测试方法,而不必担心它可能如何影响整个包含的 fsx 对象。

交换 impls 进行测试

假设你有一个名为 readConfigFile() 的函数,它使用了来自 node-fsxfsx 单例来读取名为 config.json 的文件,当测试这个函数时,你不想让它实际访问文件系统,你可以把 fsx 的实现换成 fsx-memory 提供的内存文件系统实现,如下:

import { fsx } from "fsx-node";
import { MemoryFsxImpl } from "fsx-memory";
import { readConfigFile } from "../src/example.js";
import assert from "node:assert";describe("readConfigFile()", () => {beforeEach(() => {fsx.setImpl(new MemoryFsxImpl());});afterEach(() => {fsx.resetImpl();});it("should read config file", async () => {await fsx.write("config.json", JSON.stringify({ found: true });const result = await readConfigFile();assert.isTrue(result.found);});});

这就是使用 fsx 在内存中模拟整个文件系统是多么容易。您不必像模块加载器拦截那样担心导入所有测试模块的顺序,也不需要经历包含模拟库的过程以确保一切正常。您只需更换测试的 impl,然后再重置它。通过这种方式,您可以以更高性能且不易出错的方式测试文件系统操作。

命名注意事项

不幸的是,在我发布 fsx 的时候,亚马逊发布了一款名为 FSx 的产品。如果它获得任何支持,我可能会重命名这个库,欢迎提出建议。

希望得到结论和反馈

长期以来,我们一直在使用 JavaScript 运行时中笨拙的低级文件系统 API。fsx 库是我尝试重新想象现代文件系统 API 的样子,如果我们花一些时间关注最常见的情况,并改进 JavaScript 语言目前提供的人体工学设计。通过从头开始重新思考,我认为 fsx 为我们提供了一种更愉快的文件系统体验。

基础库只关注我最常用的方法,但我计划在了解和思考用例后添加更多方法。您今天就可以试用,欢迎反馈。我很想知道你的想法!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/646695.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Chatgpt的崛起之路

Chatgpt的崛起之路 背景与发展历程背景发展历程 技术原理第一阶段:训练监督策略模型第二阶段:训练奖励模型第三阶段:采用强化学习来增强模型的能力。 国内使用情况及应用的领域面临的数据安全挑战与建议ChatGPT获取数据产生的问题数据泄露问题…

@Scheduled笔记240124

Scheduled的参数 常用的Scheduled注解属性: fixedRate属性:根据固定的频率执行任务。 Scheduled(fixedRate 5000) // 每隔5秒执行一次fixedDelay属性:任务完成后,等待一段固定的时间再执行下一次。 Scheduled(fixedDelay 5000…

HJ12 字符串反转【C语言】

【华为机试题 HJ12】字符串反转 描述输入描述:输出描述:示例1参考代码1描述 接受一个只包含小写字母的字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000) 输入描述: 输入一行,为一个只包含小写字母的字符串。 输出描述: 输出该字符串反转后的字符串…

Unity串口通信教程:基础知识和实践指南

概述 Unity在游戏开发和实时应用中广泛使用。除了图形渲染和游戏逻辑,Unity还能与外部硬件设备进行串口通信。本文将介绍如何在Unity中实现串口通信,包括基础设置、数据读写、数据校验和异或操作。 基础设置 引入命名空间 首先,需要引入Syste…

接口自动化测试:mock server之Moco工具

什么是mock server mock:英文可以翻译为模仿的,mock server是我们用来解除依赖(耦合),假装实现的技术,比如说,前端需要使用某些api进行调试,但是服务端并没有开发完成这些api&#…

测试人年终总结:入行三年,下一步怎么走,思想碰撞

原贴地址:入行三年,下一步怎么走,思想碰撞 TesterHome 熟悉环境,进步缓慢;停止思考,举步不前(为什么会有这篇文章why 初心变质:计算机系毕业,毕业时的打算是从测试进&a…

程序员如何保持身心健康

程序员要保持身心健康,可以注意以下几个方面: 饮食健康:保持均衡的饮食,多吃蔬菜水果,减少油腻和高热量食物的摄入。同时,适当饮水,避免因长时间坐着工作而导致的脱水。尽量不要吃街边摊、大排…

#常见问题总结#在docker中跑前端vue项目

目录 前言一、no such file or directory, open...总结 前言 提示:这里可以添加本文要记录的大概内容: 记录在docker中跑前端项目过程中,我遇到的问题以及解决方法 提示:以下是本篇文章正文内容,下面案例可供参考 一…

大数据数据可视化工具ECharts,从入门到精通!

介绍:ECharts是一个强大的数据可视化图表库,它基于JavaScript开发,并具有丰富的特性和灵活性。 多平台支持:ECharts可以在PC和移动设备上流畅运行,它对移动端进行了优化,确保在不同设备上都有良好的展示效果…

为什么游戏公司开发一个游戏需要上亿资金

** 为什么游戏公司开发一个游戏需要上亿资金 ** 游戏界有句老话:要做游戏,没上亿准备别来碍手碍脚。说直白点,就是要想在这个竞争激烈的蓝海里立足,开发一款像模像样的游戏,需要有几亿元的资金准备。 可能有人会觉得…

互联网泛人才流动报告:大厂扩张按下暂停键,这家公司逆势给出5w月薪招人

前段时间,脉脉高聘人才智库发布了《2023年互联网泛人才流动报告》,似乎佐证了23年是互联网真正的寒冬…… 卷生卷死!5个人竞争2个岗位 2023年,互联网行业的求职难度可以说是地狱级别,人才供需比持续上升,…

什么是Spring

文章目录 什么是Spring什么是 IoC Spring的IoCDI的概念 什么是Spring Spring 是一个包含了众多工具方法的 IoC容器。 什么是 IoC Inversion of Control — 控制反转 在传统的开发中,假设A类依赖于B类,那么创建A对象实例就需要先new一个B类对象&#x…

分享本机搭建《幻兽帕鲁》服务,并可以外网联机的方法

由于《幻兽帕鲁》玩家好评率也高达93%,天卖出600万份,爆火游戏幻兽帕鲁最高180万人同时在线,直接登顶,也有不少玩家从中体会到了难得的“当老板”的乐趣。官方虽然支持联机人数更多,但是由于游戏过于火爆,服…

瑞_力扣LeetCode_101. 对称二叉树

文章目录 题目 101. 对称二叉树题解方式一 递归方式二 迭代 🙊 前言:本文章为瑞_系列专栏之《刷题》的力扣LeetCode系列,主要以力扣LeetCode网的题进行解析与分享。本文仅供大家交流、学习及研究使用,禁止用于商业用途&#xff0c…

代码随想录算法训练营第十六天| 104.二叉树的最大深度 ● 111.二叉树的最小深度 ● 222.完全二叉树的节点个数

104.二叉树的最大深度 本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。 ●二叉树节点的深度:指从根节点到该节点的最长简单路径边…

Java Web(四)--JavaScript

介绍 JavaScript 教程 JavaScript 能改变 HTML 内容,能改变 HTML 属性,能改变 HTML 样式 (CSS),能完成页面的数据验证; JS 需要运行浏览器来解析执行JavaScript 代码; JS 是 Netscape 网景公司的产品&#xf…

JDK1.8新的时间类

LocalDateTime 特点: 没有时区的日期时间对象;是一个不可变的日期对象,能获取到对象中的日期、时间、年月日、星期几、等属性;是线程安全的。 // 解析模板 private static final DateTimeFormatter DTF DateTimeFormatter.ofP…

深入了解Figure的结构与层次

深入了解Figure的结构与层次 一 Matplotlib中的Figure1.1 Figure的概念和作用:1.2.创建Figure对象:1.3 Figure的属性和方法: 二 子图(Axes)的角色与创建2.1 子图(Axes)的概念:2.2 创建子图的方法:2.3 Axes的…

使用visual studio写一个简单的c语言程序

官网下载visual studio,社区版免费的 https://visualstudio.microsoft.com/zh-hans/ 下载好以后选择自己的需求进行安装,我选择了两个,剩下的是默认。 创建文件:

01背包及例题

有很多种写法,二维数组,一维数组等,这里只写滚动数组,这是优化最大的,而且往后完全背包问题用的到 板子 v v v是容量, c [ i ] c[i] c[i]是每个的体积, w [ i ] w[i] w[i]是每个的价值 for (i…