Node.js「二」—— fs 模块 / async 与 await

本文为 Node.js 系列笔记第二篇。文章参考:nodejs 教程 —— 大地;《深入浅出 Node.js》;阮一峰 nodejs 博客; 添加链接描述

文章目录

  • 一、fs 模块常用读写操作
    • 1. fs.start: 判断文件 or 目录
    • 2. fs.mkdir: 创建目录
    • 3. fs.writeFile: 写入数据
    • 4. fs.appendFile: 追加数据
    • 5. fs.readFile: 读取文件内容
    • 6. fs.readdir: 读取文件目录
    • 7. fs.rename: 重命名
    • 8. fs.rmdir: 删除目录
    • 9. fs.unlink: 删除文件
    • 10. fs 模块应用
  • 二、async 与 await
  • 三、fs 中的流
    • 1. fs.createReadStream
    • 2. fscreateWriteStream()
    • 3. 管道流

一、fs 模块常用读写操作


fs 是 filesystem 的缩写,该模块主要用于对系统文件及目录进行读写操作,它是 Node 的内置模块。下面介绍一下相关的常用 API 。
 

1. fs.start: 判断文件 or 目录


检测是文件还是目录。示例如下:

在这里插入图片描述

const fs = require('fs');

fs.stat('./html', (err, stats) => {
    if (err) {
        console.log(err);
        return;
    }
    console.log(`是否为文件:${stats.isFile()}`);            // 是否为文件:false
    console.log(`是否为目录:${stats.isDirectory()}`);       // 否为目录:true
})

 

2. fs.mkdir: 创建目录


以异步的方式用于创建目录。mkdir 接受三个参数。

fs.mkdir(path, [mode], [callback(err)])
  1. path:将创建的目录路径
  2. mode:可选。目录权限(读写权限),默认0777
  3. callback :可选。回调函数,传递异常参数err
  • 示例
	const fs = require('fs');
	
	fs.mkdir('./css', (err) => {
	    if (err) {
	        console.log(err);
	        return;
	    }
	    console.log('创建成功');    // 创建成功
	})

         Node.js「二」—— fs 模块 / async 与 await_第1张图片
 

3. fs.writeFile: 写入数据


以异步的方式创建文件并将 data 写入文件,文件已存在的情况下,原内容将被替换。

fs.writeFile(filename, data, [options], [callback(err)])
  1. filename :文件名称

  2. data (String | Buffer) :将要写入的内容,可以使字符串或 buffer 数据。

  3. options (Object):可选。option 数组对象,包含:

    1)encoding :可选。默认 ‘utf8′,当 data 使 buffer 时,该值应该为 ignored。
    2)mode:文件读写权限,默认值 438
    3)flag:默认值 ‘w’

  4. callback:可选。回调函数,传递一个异常参数 err。

  • 示例
	const fs = require('fs');
	
	fs.writeFile('./html/index.html', 'Hello World', (err) => {
	    if (err) {
	        console.log(err);
	        return;
	    }
	    console.log('写入文件成功');        // 写入文件成功
	})

         在这里插入图片描述
 

4. fs.appendFile: 追加数据


将 data 插入到文件里,如果文件不存在会自动创建。与 fs.writeFile 的区别是,如果文件存在原内容不会被覆盖,而是进行追加。

fs.appendFile(filename, data, [options], callback)
  • 示例
	const fs = require('fs');
	
	fs.appendFile('./html/index.html', '\nnode.js', (err) => {
	    if (err) {
	        console.log(err);
	        return;
	    }
	    console.log('appendFile 成功');
	})

         在这里插入图片描述
 

5. fs.readFile: 读取文件内容


以异步的方式读取文件内容。不置顶内容编码的情况下,将以 buffer 的格式输出,如:

fs.readFile(filename, [encoding], [callback(err,data)])
  1. filename:文件路径
  2. options:可选。option 对象,包含 encoding,编码格式。
  3. callback:可选。回调函数,2 个参数,异常 err 和文件内容 data(Buffer)。
  • 示例
	const fs = require('fs');
	
	fs.readFile('./html/index.html', (err, data) => {
	    if (err) {
	        console.log(err);
	        return;
	    }
	    console.log(data);  
	    // 打印 
	});

可以利用 toString() 将 Buffer 类型转换为 String:

    console.log(data.toString());
    // Hello World
    // node.js

 

6. fs.readdir: 读取文件目录


以异步的方式读取文件目录。

fs.readdir(path, [callback(err,files)])
  1. path:目录路径。
  2. callback :可选。回调函数,传递两个参数 err 和 files(string[]),files 是一个包含 “ 指定目录下所有文件名称的” 数组。
  • 示例
	const fs = require('fs');
	
	fs.readdir('./day04', (err, files) => {
	    if (err) {
	        console.log(err);
	        return;
	    }
	
	    console.log(files);     // [ 'index.html' ]
	})

 

7. fs.rename: 重命名


修改文件名称,可更改文件的存放路径。

fs.rename(oldPath, newPath, [callback(err)])
  1. oldPath:原路径。
  2. newPath:新路径。
  3. callback:回调函数。传递一个 err 异常参数。
  • 示例
	const fs = require('fs');
	
	fs.rename('./css/a.css', './css/index.css', (err) => {
	    if (err) {
	        console.log(err);
	        return;
	    }
	
	    console.log('更改成功');    // 更改成功
	})

         在这里插入图片描述
这里是并没有改变文件路径而只是改变了文件名称,如果想要改变文件路径,只需要将新路径改变为其他文件夹下即可,如 './html/index.css'

 

8. fs.rmdir: 删除目录


以异步的方式删除文件目录。

fs.rmdir(path, [callback(err)])
  • 示例
	const fs = require('fs');
	
	fs.rmdir('./css', (err) => {
	    if (err) {
	        console.log(err);       // 保错:directory not empty
	        return;
	    }
	
	    console.log('删除目录成功');
	})

上例报错:directory not empty,说明:当目录中还有其他文件时不能直接删除目录。

 

9. fs.unlink: 删除文件

删除文件操作。

fs.unlink(path, [callback(err)])
  • 示例
	const fs = require('fs');
	
	fs.unlink('./css/index.css', (err) => {
	    if (err) {
	        console.log(err);
	        return;
	    }
	
	    console.log('删除文件成功');    // 删除文件成功
	})

 

10. fs 模块应用


需求:对于如下图所示文件夹 wwwroot,找出 wwwroot 目录下面所有的目录(css、img、js),并放到数组中。
Node.js「二」—— fs 模块 / async 与 await_第2张图片
当然,我们首先想到的是利用 for 循环遍历调用,判断是否为目录。实现代码(错误写法)如下所示:

const fs = require('fs');

var path = './wwwroot';
var dirArr = [];

fs.readdir(path, (err, data) => {
    if (err) {
        console.log(err);
        return;
    }
    console.log(data);  // [ 'book.txt', 'css', 'img', 'index.html', 'js' ]

    for (var i = 0; i < data.length; i++) {
        fs.stat(path + '/' + data[i], (error, stats) => {
            if (stats.isDirectory()) {
                dirArr.push(data[i]);
            }
        })
    }
    console.log(dirArr);    // []
})


通过打印结果可以看出,这是有问题的。因为 fs.stat 是异步方法,其回调函数内容会放到任务队列中等待执行,当主程序执行栈中任务执行过后,才会被执行。

也就是说,console.log(dirArr) 会被先执行,之后才会执行 fs.stat 的回调函数。

  • 解决方法

为了解决这个问题,可以使用闭包递归执行

fs.readdir(path, (err, data) => {
    if (err) {
        console.log(err);
        return;
    }
    console.log(data);  	// [ 'book.txt', 'css', 'img', 'index.html', 'js' ]


    (function getDir(i) {
        if (i == data.length) {
            console.log(dirArr);	// [ 'css', 'img', 'js' ]
            return;
        }
        fs.stat(path + '/' + data[i], (error, stats) => {
            if (stats.isDirectory()) {
                dirArr.push(data[i]);
            }
            getDir(i + 1);
        })
    })(0)
})

但是这段代码看起来还是那么复杂,可行执行较差。除此之外,我们可以使用 Node 中新特性 async await 来解决此方法,这里暂时不先介绍,感兴趣的可参考下文。

 

二、async 与 await


async/await 是一个期待已久的 JavaScript 特性,让我们更好的理解使用异步函数。它建立在 Promises上,并且与所有现有的基于 Promise 的 API 兼容。

  1. async 是 ES7 中新增内容,对于异步操作的解决方案,它是 Generator 函数的语法糖。
  2. async 和 await 使代码更简洁,语义化。它把异步执行的代码写得像同步代码那样直观,而不是像 Promise 一样有各种 then
  3. async 和 await 保证非阻塞。async 的函数中可以有一个或多个异步操作,一旦遇到 await 就会立即返回一个 pending 状态的 Promise 对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行。
  4. 把异步变同步:在 async 函数内部异步代码就像被同步执行的那样(继发执行)。注意,并不是指它会阻塞主线程一直等待异步调用返回。

下面说一下 async 和 await 的语法。

  • async:声明一个异步函数
	async function name([param]) {
	    statements 
	}

几点说明

  1. 自动将常规函数转换成 Promise,返回值也是一个 Promise 对象
  2. 只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数
  3. 异步函数内部可以使用 await

举个例子

	async function test() {
	    return 'Promise';
	}
	
	console.log(test());        // Promise { 'Promise' }
  • await:等待异步方法执行完成

他几点说明

  1. 放置在 Promise 调用之前,await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。
  2. 若 Promise 正常处理(fulfilled),其回调的 resolve 函数参数作为 await 表达式的值,继续执行 async function
  3. 若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。

举个例子

	async function test() {
	    return 'Promise';
	}
	
	async function main() {
	    var data = await test();
	    console.log(data);
	}
	main();         	// 打印 Promise
  • 示例

从外部获取异步方法里面的数据。代码如下:

	function test() {
	    return new Promise((resolve, reject) => {
	        setTimeout(() => {
	            var name = '张三';
	            resolve(name);
	        }, 1000);
	    })
	}
	
	async function main() {
	    var data = await test();	
    	// 等待获取 test() 中 Promise 处理结果,也就是等待获取 resolve 返回的参数 value 
	    
	    console.log(data);
	}
	main();             // 打印 张三
  • 案例改善

可以利用 async/await 对上一节遗留的问题进行改善,问题如下:

需求:对于如下图所示文件夹 wwwroot,找出 wwwroot 目录下面所有的目录(css、img、js),并放到数组中。
Node.js「二」—— fs 模块 / async 与 await_第3张图片
上节最后使用的是递归利用闭包解决了异步的问题,这里我们可以使用 async / await 将异步操作变为同步以此解决问题。见下面代码:

const fs = require('fs');

async function isDir(path) {
    return new Promise((resolve, reject) => {
        // 判断文件还是目录
        fs.stat(path, (error, stats) => {
            if (error) {
                console.log(error);
                reject(error);
            }

            if (stats.isDirectory()) {
                resolve(true);
            } else {
                resolve(false);
            }
        })
    })
}

function main() {
    var path = './wwwroot';
    var dirArr = [];

    // 注意 await 应该只能在 async function 内部使用
    fs.readdir(path, async (err, data) => {
        if (err) {
            console.log(err);
            return;
        }
        for (var i = 0; i < data.length; i++) {
            if (await isDir(path + '/' + data[i])) {    // 利用 await 将异步操作改为同步
                dirArr.push(data[i]);
            }
        }
        console.log(dirArr);
    })
}
main();

 

三、fs 中的流


1. fs.createReadStream

createReadStream 方法往往用于打开大型的文本文件,创建一个读取操作的数据流。所谓大型文本文件,指的是文本文件的体积很大,读取操作的缓存装不下,只能分成几次发送,每次发送会触发一个data 事件,发送结束会触发 end 事件。

	const fs = require('fs');
	
	var readStream = fs.createReadStream('./data/input.txt');
	
	var count = 0;
	var str = '';
	
	readStream.on('data', (data) => {
	    str += data;
	    count++;
	})
	
	readStream.on('end', () => {
	    console.log(str);
	    console.log(count);
	})
	
	readStream.on('error', (err) => {
	    console.log(err);
	})

2. fscreateWriteStream()

createWriteStream 方法创建一个写入数据流对象,该对象的 write 方法用于写入数据,end 方法用于结束写入操作。

	var fs = require('fs');
	
	var str = '';
	
	for (var i = 0; i < 500; i++) {
	    str += '太阳当空照,花儿对我笑\n';
	}
	
	var writeStream = fs.createWriteStream('./data/output.txt');
	writeStream.write(str);
	
	// 标记写入完成
	writeStream.end();
	
	writeStream.on('finish', () => {
	    console.log('写入完成');
	})

注意:必须利用 writeStream.end() 标记文件结尾后,'finish' 事件才能被执行。

 

3. 管道流


管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。利用 createWriteStream 方法和 createReadStream 方法配合,可以实现拷贝大型文件。

如下代码,拷贝一个图片 nodejs.png。

const fs = require('fs');

var readStream = fs.createReadStream('./nodejs.png');
var writeStream = fs.createWriteStream('./data/nodejs.png');

readStream.pipe(writeStream);

你可能感兴趣的:(Node.js,node.js,javascript,前端)