这个终端日志是 Node JS 异步性质的结果吗?

问题描述

我没有发现任何关于此的具体信息,这不是真正的问题,但我想更好地了解这里发生的事情。

基本上,我正在测试一些简单的 NodeJS 代码,如下所示:

//Summary : Open a file,write to the file,delete the file.

let fs = require('fs');

fs.open('mynewfile.txt','w',function(err,file){
    if(err) throw err;
    console.log('Created file!')
})

fs.appendFile('mynewfile.txt','Depois de ter criado este ficheiro com o fs.open,acrescentei-lhe data com o fs.appendFile',function(err){
    if (err) throw err;
    console.log('Added text to the file.')
})

fs.unlink('mynewfile.txt',function(err){
    if (err) throw err;
        console.log('File deleted!')
})

console.log(__dirname);

我认为这段代码会按照从上到下编写的顺序执行,但是当我查看终端时,我不确定是不是这样,因为这是我得到的:

$ node FileSystem.js 
C:\Users\Simon\OneDrive\Desktop\Portfolio\Learning Projects\NodeJS_Tutorial
Created file!
File deleted!
Added text to the file.

//Expected order would be: Create file,add text to file,delete file,log dirname.

最终,当我查看我的文件夹时,代码顺序似乎仍然以某种方式被遵循,而不是终端可能让您想到的,因为文件已被删除并且我在目录中没有任何东西。

所以,我想知道,为什么终端的登录顺序与代码从上到下编写的顺序不同。 这是 NodeJS 异步性质的结果还是其他原因?

解决方法

代码(原则上)按照你说的从上到下执行。但是 fs.openfs.appendFilefs.unlink 是异步的。即,它们按特定顺序放置在执行堆栈上,但无法保证它们以何种顺序完成,因此您无法保证回调以何种顺序执行。如果多次运行代码,很有可能会遇到不同的执行顺序......

如果您需要特定的订单,您有两种不同的选择

  1. 你只在前一个的回调中调用后一个操作,即类似于下面的内容

    fs.open('mynewfile.txt','w',function(err,file){
      if(err) throw err;
      console.log('Created file!')
    
      fs.appendFile('mynewfile.txt','...',function(err){
        if (err) throw err;
        console.log('Added text to the file.')
    
        fs.unlink('mynewfile.txt',function(err){
          if (err) throw err;
          console.log('File deleted!')
        })
      })
    })
    

    你看,随着嵌套的增加,这些代码变得非常难看且难以阅读......

  2. 您切换到基于承诺的方法

    let fs = require('fs').promises;
    
    fs.open("myfile.txt","w")
      .then(file=> {
        return fs.appendFile("myfile.txt","...");
      })
      .then(res => {
        return fs.unlink("myfile");
      })
      .catch(e => {
         console.log(e);
      })
    

    使用 promise 版本的操作,您还可以使用 async/await

    async function doit() {
       let file = await fs.open('myfile.txt','w');
       await fs.appendFile('myfile.txt','...');
       await fs.unlink('myfile.txt','...');
    }
    

对于所有三种可能性,您可能需要先关闭文件,然后才能取消链接。

有关更多详细信息,请阅读 Promise、async/await 和 Javascript 中的执行堆栈

,

这是两件事的结合:

  • Node.js 的异步特性,正如您正确假设的
  • 能够取消链接打开的文件

可能发生的事情是这样的:

  • 同时打开和创建文件(open 带有标志 w
  • 第二次打开文件以追加 (fs.appendFile)
  • 文件已取消链接
  • 数据已附加到文件(虽然它已取消链接)并且文件已关闭

当数据被追加时,该文件仍然作为一个 inode 存在于磁盘上,但它的硬链接(引用)为零。它仍然会占用空间,但操作系统会在关闭时检查引用计数,如果计数降至零,则释放空间。

人们有时会遇到类似的情况,例如使用日志轮换的 HTTP 服务器等守护进程:如果在切换日志时出现问题,旧的日志文件可能会被取消链接但不会关闭,因此它永远不会被清理并占用空间永远(直到您重新启动或重新启动进程)。

请注意,您观察到的操作顺序是随机的,并且它们可能会重新排序。不要依赖它。

,

你可以把它写成(未经测试):

let fs = require('fs');

const main = async () => {
  await fs.open('mynewfile.txt','w');
  await fs.appendFile('mynewfile.txt','content');
  await fs.unlink('mynewfile.txt');
});

main()
  .then(() => console.log('success'()
  .catch(console.error);

或在另一个异步函数中:

const someOtherFn = async () => {
   try{
      await main();
   } catch(e) {
     // handle any rejection to your liking
   }
}

(catch 块不是强制性的。您可以选择让它们扔到顶部。这只是为了展示 async / await 如何让您使同步代码看起来好像是同步代码,而不会遇到回调地狱。 )