我刚刚开始学习Node.js,并立即使用下面的代码进行学习:
const fs = require(`fs`);
fs.appendFile(`mynewfile1.txt`, `Hello content!`, err => {
if (err) throw err;
console.log(`Saved!`);
});
fs.rename(`mynewfile1.txt`, `myrenamedfile.txt`, err => {
if (err) throw err;
console.log(`File Renamed!`);
});
它位于app.js
中,并且在运行之前此目录中没有其他文件。
当我运行它(使用node app.js
bash命令)时,它稳定输出:
> File Renamed!
> Saved!
我知道这两种操作都是异步运行的,并且我认为重命名文件必须比创建新文件快,但是如果我们尝试重命名的文件不存在,那么fs.rename
应该会抛出,那么如果不存在第一条语句,该怎么办。否则,它会假装一切正常。
我认为发生这种情况是因为在执行昂贵的I / O节点之前以某种方式评估了操作,就我而言,不是立即创建新文件并重命名,而是立即创建名为myrenamedfile.txt
的文件。 / p>
我的问题是:
fs.rename
行为的规则吗?我的意思是,因为它显然并没有实际重命名现有文件(因为它当时尚未创建),所以它应该做些不同的事情。那么,“不同之处”的文档在哪里? The main documentation未指定。UPD:
非常感谢所有迄今为止做出回应的好人,我知道promisify
模块中的utils
或实验性require('fs').promises
都可以用来链接操作,但是问题是关于为什么代码有效并且不会引发错误,而不是如何将操作链接在一起。
答案 0 :(得分:1)
如果“文件重命名!”首先执行“已保存!”部分将创建一个名为mynewfile1.txt
如果要保留订单,请在异步函数中使用
const fs = require(`fs`);
await fs.appendFile(`mynewfile1.txt`, `Hello content!`);
console.log(`Saved!`);
await fs.rename(`mynewfile1.txt`, `myrenamedfile.txt`);
console.log(`File Renamed!`);
答案 1 :(得分:1)
是的,您对异步流的假设是正确的。由于文件最初不存在,并且无论 appendFile 是否完成,都将触发重命名,因此它将抛出错误no such file or directory
。
控制台显示:File Renamed!
,因为您只是无条件打印了它。
要处理这种情况,请分别使用以下 sync方法:
const fs = require(`fs`);
fs.appendFileSync(`mynewfile1.txt`, `Hello content!`);
console.log(`Saved!`);
fs.renameSync(`mynewfile1.txt`, `myrenamedfile.txt`);
console.log(`File Renamed!`);
或使用异步/等待:
const fs = require(`fs`);
const util = require('util');
const appendAndRename = async () => {
try {
await util.promisify(fs.appendFile)(`mynewfile1.txt`, `Hello content!`);
console.log(`Saved!`);
await util.promisify(fs.rename)(`mynewfile1.txt`, `myrenamedfile.txt`);
console.log(`File Renamed!`);
} catch(err) {
console.log(err);
throw err;
}
}
appendAndRename();
确保您的 await 语句始终包裹在 try / catch 块中,并使用 async 关键字声明直接父函数。
希望这会有所帮助:)
答案 2 :(得分:1)
在下面找到有效的(经过测试的)代码段。使用内置的“ util”模块来实现fs功能。
const fs = require(`fs`);
const util = require('util');
async function doTheOpAsync() {
const appendFile = util.promisify(fs.appendFile);
const rename = util.promisify(fs.rename);
try{
await appendFile(`mynewfile1.txt`, `Hello content!`);
console.log(`Saved!`);
await rename(`mynewfile1.txt`, `myrenamedfile.txt`);
console.log(`File Renamed!`);
}catch(ex){
console.log(ex);
}
}
doTheOpAsync();
答案 3 :(得分:0)
好吧,我终于找到了一些时间来研究这个话题,而我的发现似乎可以很好地解释正在发生的事情。这是最可能的答案:
Node既不预编译代码也不“分批更新/操作”(例如,React这样做)。
fs.rename,是在编译器到达其被调用的位置并且直到这里都没有黑魔法发生时才执行的。
Node documentation引用a Linux manual page暗示fs.rename
(我使用的at least on Unix-based operational systems)的行为与Linux rename
完全相同。
因此,我们进入了操作系统级别的命令。
现在,在基于Unix的操作系统上的操作大部分是atomic operations,这(在非常基本的水平上)意味着以无法区分任何部分的方式执行操作。因此,不能将操作分成较小的部分。
对于我们的特定情况,很重要的一点是要知道,这个概念意味着在执行某个操作时,只有该操作本身才能感知到它正在执行的任务的任何进度。仅当操作100%完成时,系统的其余部分才会开始查看操作结果。当有99%的工作是从手术外部完成时,无法感知状态。否则,该操作将不是原子操作。
现在,有几个引用(a comment on PHP Docs,an answer on askubuntu,an answer on serverfault)表示在重命名操作之前开始的任何写/读操作将在原子操作世界中和平地继续。 / p>
考虑到fs.appendFile
只是fs.open
和fs.write
组合的包装,我们可以说fs.appendFile
是不是原子的
因此,很可能是在基于Unix的系统上运行上述脚本时发生的情况:
fs.appendFile
已到达。 It's code被执行。fs.appendFile
本身是同步的,因此将完全执行,直到调用fs.writeFile
。fs.open
的调用。因此,fs.writeFile
一直执行到调用fs.open
为止,然后将异步操作添加到队列中。 JS编译器现在可以返回到最初执行的文件(我们的源文件)。fs.rename
并开始执行。fs.open
已解决,因为创建空文件的速度非常快。但是,appendFile
尚未完成运行,因为尚未开始写入操作。编译器将对writeAll
的调用添加到异步队列中。fs.rename
正在执行,而我们已经有了该文件,但它仍为空。但是,这并不重要,因为所需的文件是存在的,因此,重命名成功并且File Renamed!
被打印到控制台。Saved!
已打印到控制台。不幸的是,我无法通过测试来证实自己的猜测,因为显然不可能在fs.rename
回调中执行任何有意义的事情(例如读取文件内容以确认其为空),因为操作系统操作比JS代码。另外,也无法暂停/推迟/阻止它们。
如果有任何node.js大师会读过这篇文章,请告诉我我的想法是否正确。
最后,我找到了一种检验我的猜测的方法:
如果我写入大量数据(例如'Hello content!'.repeat(9e6)
),则在写操作完成之前,我有足够的时间在fs.statSync
回调中调用fs.rename
。它报告该文件存在,并且大小为0。
如果我随后在appendFile
回调中调用相同的文件,它将报告所有相同的文件,但文件的size
现在为126000000
。
而且,最重要的是,如果我添加以下行:
console.log(execSync('lsof myrenamedfile.txt', {encoding: 'utf8'}));
在rename
回调中,我可以看到仍在写入文件的节点进程,如下所示:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 50837 username 20w REG 1,4 0 15997608 myrenamedfile.txt