接前面
- JavaScript基础学习:ECMAScript 基础知识
- Node.js学习笔记(一):Node.js是什么,如何安装,如何简单使用,工作目录与脚本所在目录获取
- Node.js学习笔记(二):命令行参数、环境变量、标准输入、标准输出、标准出错、信号处理、程序返回值,打包
- Node.js学习笔记(三):fs模块API,三种风格——同步、异步回调、异步promise,三种层级——底层、高层、流
继续了解Node.js。看看其内置模块 fs
-
三种风格:同步API、异步回调API、异步promise API。
-
三种抽象层级:底层API、高层API、流 API。
fs 的API 风格
fs模块提供了三套不同风格的 API:
- 同步API
- 异步回调API
- 异步promise API
操作 | 同步 API | 异步回调 API | Promise API |
---|---|---|---|
创建文件 | fs.openSync(file, 'w') |
fs.open(file, 'w', callback) |
fsPromises.open(file, 'w') |
写入内容 | fs.writeSync(fd, data) |
fs.write(fd, data, callback) |
fd.write(data) |
关闭文件 | fs.closeSync(fd) |
fs.close(fd, callback) |
fd.close() |
打开文件 | fs.openSync(file, 'r') |
fs.open(file, 'r', callback) |
fsPromises.open(file, 'r') |
读取内容 | fs.readSync(fd, buffer, ...) |
fs.read(fd, buffer, ..., callback) |
fd.read(buffer, ...) |
删除文件 | fs.unlinkSync(file) |
fs.unlink(file, callback) |
fsPromises.unlink(file) |
文件是否存在 | fs.existsSync(file) |
fs.access(file, callback) |
fsPromises.access(file) |
接下来,用一个例子,分别看看,如何执行:创建、写入、关闭、打开、读取、删除文件等操作。
- 使用同步API 【例子1】
- 使用异步回调API
- 直接用(回调嵌套)【例子2】
- 封装成 promise 使用 then()【例子3】
- 封装成 promise 使用 await【例子4】
- 使用promises API
- 使用 then()【例子5】
- 使用 await【例子6】
例子1- 同步 API
简单直观,但是会阻塞进程。适用于简单脚本和小型程序。
import fs from 'node:fs';
import { Buffer } from 'node:buffer';
const filePath = 'dbzhang800_sync.txt';
// 创建文件并写入内容
try {
const f = fs.openSync(filePath, 'w');
fs.writeSync(f, 'Hello 1+1=10 from Node.js Sync!');
fs.closeSync(f);
console.log('File created and written successfully.');
} catch (error) {
console.error('Error:', error);
}
// 打开文件并读取内容
try {
const f = fs.openSync(filePath, 'r');
// 申请一个缓冲区
const buffer = Buffer.alloc(1024);
const bytesRead = fs.readSync(f, buffer, 0, buffer.length, 0);
const fileContent = buffer.toString('utf8', 0, bytesRead);
fs.closeSync(f);
console.log('File Content:', fileContent);
} catch (error) {
console.error('Error:', error);
}
// 删除文件
try {
fs.unlinkSync(filePath);
console.log('File deleted successfully.');
} catch (error) {
console.error('Error:', error);
}
例子2- 异步回调 API
非阻塞,适用于处理大量并发请求,不会造成性能问题。但是回调地狱(Callback Hell)可能会使代码难以维护。
import fs from 'node:fs';
import { Buffer } from 'node:buffer';
const filePath = 'dbzhang800_async.txt';
// 创建文件并写入内容
fs.open(filePath, 'w', (error, fd) => {
if (error) {
console.error('Error:', error);
return;
}
const content = 'Hello 1+1=10 from Node.js Async!';
fs.write(fd, content, (error) => {
if (error) {
console.error('Error:', error);
return;
}
fs.close(fd, (error) => {
if (error) {
console.error('Error:', error);
return;
}
console.log('File created and written successfully.');
// 打开文件并读取内容
fs.open(filePath, 'r', (error, fd) => {
if (error) {
console.error('Error:', error);
return;
}
const buffer = Buffer.alloc(1024);
fs.read(fd, buffer, 0, buffer.length, 0, (error, bytesRead) => {
if (error) {
console.error('Error:', error);
return;
}
const fileContent = buffer.toString('utf8', 0, bytesRead);
fs.close(fd, (error) => {
if (error) {
console.error('Error:', error);
return;
}
console.log('File Content:', fileContent);
// 删除文件
fs.unlink(filePath, (error) => {
if (error) {
console.error('Error:', error);
return;
}
console.log('File deleted successfully.');
});
});
});
});
});
});
});
例子3- 异步回调 API,封装成 promise,then()
要避免多层嵌套造成的回调地狱额,尝试使用EMCAScript的Promise进行改写
import { Buffer } from 'node:buffer';
import fs from 'node:fs';
const filePath = 'dbzhang800_async.txt';
function openFileAsync(path, flags) {
return new Promise((resolve, reject) => {
fs.open(path, flags, (error, fd) => {
if (error) {
reject(error);
} else {
resolve(fd);
}
});
});
}
function writeFileAsync(fd, content) {
return new Promise((resolve, reject) => {
fs.write(fd, content, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
function closeFileAsync(fd) {
return new Promise((resolve, reject) => {
fs.close(fd, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
function openAndReadFileAsync(path, flags, buffer, offset, length, position) {
return new Promise((resolve, reject) => {
fs.open(path, flags, (error, fd) => {
if (error) {
reject(error);
} else {
fs.read(fd, buffer, offset, length, position, (error, bytesRead) => {
if (error) {
reject(error);
} else {
resolve({ fd, bytesRead, buffer });
}
});
}
});
});
}
function unlinkFileAsync(path) {
return new Promise((resolve, reject) => {
fs.unlink(path, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
openFileAsync(filePath, 'w')
.then((fd) => {
const content = 'Hello 1+1=10 from Node.js Async!';
return writeFileAsync(fd, content)
.then(() => closeFileAsync(fd))
.then(() => {
console.log('File created and written successfully.');
return openAndReadFileAsync(filePath, 'r', Buffer.alloc(1024), 0, 1024, 0);
});
})
.then(({ fd, bytesRead, buffer }) => {
const fileContent = buffer.toString('utf8', 0, bytesRead);
return closeFileAsync(fd)
.then(() => fileContent);
})
.then((fileContent) => {
console.log('File Content:', fileContent);
// 删除文件
return unlinkFileAsync(filePath);
})
.then(() => {
console.log('File deleted successfully.');
})
.catch((error) => {
console.error('Error:', error);
});
例子4- 异步回调 API,封装成 promise,await
再使用await 取代then写法,改写如下:
import { Buffer } from 'node:buffer';
import fs from 'node:fs';
const filePath = 'dbzhang800_async.txt';
function openFileAsync(path, flags) {
return new Promise((resolve, reject) => {
fs.open(path, flags, (error, fd) => {
if (error) {
reject(error);
} else {
resolve(fd);
}
});
});
}
function writeFileAsync(fd, content) {
return new Promise((resolve, reject) => {
fs.write(fd, content, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
function closeFileAsync(fd) {
return new Promise((resolve, reject) => {
fs.close(fd, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
function openAndReadFileAsync(path, flags, buffer, offset, length, position) {
return new Promise((resolve, reject) => {
fs.open(path, flags, (error, fd) => {
if (error) {
reject(error);
} else {
fs.read(fd, buffer, offset, length, position, (error, bytesRead) => {
if (error) {
reject(error);
} else {
resolve({ fd, bytesRead, buffer });
}
});
}
});
});
}
function unlinkFileAsync(path) {
return new Promise((resolve, reject) => {
fs.unlink(path, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
try {
const fd = await openFileAsync(filePath, 'w');
const content = 'Hello 1+1=10 from Node.js Async!';
await writeFileAsync(fd, content);
await closeFileAsync(fd);
console.log('File created and written successfully.');
const { fd: readFd, bytesRead, buffer } = await openAndReadFileAsync(
filePath,
'r',
Buffer.alloc(1024),
0,
1024,
0
);
const fileContent = buffer.toString('utf8', 0, bytesRead);
await closeFileAsync(readFd);
console.log('File Content:', fileContent);
// 删除文件
await unlinkFileAsync(filePath);
console.log('File deleted successfully.');
} catch (error) {
console.error('Error:', error);
}
例子5- 异步promise API,使用then
模块导入,官方手册给出写法:
import fsPromises from 'node:fs/promises';
可能这个更容易理解:
import { promises as fsPromises } from 'node:fs';
使用 Promise 对象,重写前面的例子。相比回调,可以更容易处理异步操作的结果和错误。
import fsPromises from 'node:fs/promises';
const filePath = 'dbzhang800_async.txt';
// 创建文件并写入内容
fsPromises.open(filePath, 'w')
.then((fd) => {
const content = 'Hello 1+1=10 from Node.js Async!';
return fd.write(content)
.then(() => fd.close())
.then(() => {
console.log('File created and written successfully.');
// 打开文件并读取内容
return fsPromises.open(filePath, 'r');
});
})
.then((fd) => {
const buffer = Buffer.alloc(1024);
return fd.read(buffer, 0, buffer.length, 0)
.then((result) => ({ fd, bytesRead: result.bytesRead, buffer }));
})
.then(({ fd, bytesRead, buffer }) => {
const fileContent = buffer.toString('utf8', 0, bytesRead);
return fd.close().then(() => fileContent);
})
.then((fileContent) => {
console.log('File Content:', fileContent);
// 删除文件
return fsPromises.unlink(filePath);
})
.then(() => {
console.log('File deleted successfully.');
})
.catch((error) => {
console.error('Error:', error);
});
例子6- 异步promise API,使用await
通过 async/await
语法可以进行更清晰的异步代码编写:
import { promises as fsPromises } from 'node:fs';
const filePath = 'dbzhang800_async.txt';
// 创建文件并写入内容
try {
const fd = await fsPromises.open(filePath, 'w');
const data = 'Hello 1+1=10 from Node.js ASync!';
await fd.write(data);
await fd.close();
console.log('File created and written successfully.');
} catch (error) {
console.error('Error:', error);
}
// 打开文件并读取内容
try {
const fd = await fsPromises.open(filePath, 'r');
const buffer = Buffer.alloc(1024);
const { bytesRead } = await fd.read(buffer, 0, buffer.length, 0);
const fileContent = buffer.toString('utf8', 0, bytesRead);
await fd.close();
console.log('File Content:', fileContent);
} catch (error) {
console.error('Error:', error);
}
// 删除文件
try {
await fsPromises.unlink(filePath);
console.log('File deleted successfully.');
} catch (error) {
console.error('Error:', error);
}
API抽象层级
前面例子中只使用文件操作的底层API,它还有更高层级的封装
- 底层:open、close、read、wirte、...
- 高层:readFile、writeFile、...
- 流:createReadStream、createWriteStream、...
读文件
例子1-底层API
前面例子全都是用的底层API,所以都需要管理Buffer。
import fs from 'node:fs/promises';
const fd = await fs.open('mytest.mjs', 'r');
const buffer = Buffer.alloc(1024);
const ret = await fd.read(buffer, 0, buffer.length, 0);
await fd.close();
console.log('Read data:', buffer.toString('utf-8', 0, ret.bytesRead));
例子2-高层API
使用同步API和高层API:readFileAync
import fs from 'node:fs';
const buffer = fs.readFileSync('mytest.mjs');
console.log('Read data:', buffer.toString('utf-8'));
使用回调API和高层API:readFile
import fs from 'node:fs';
fs.readFile('mytest.mjs', 'utf-8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log('Read data:', data);
});
使用promises和高层API:readFile
import fs from 'node:fs/promises';
const buffer = await fs.readFile('mytest.mjs');
console.log('Read data:', buffer.toString('utf-8'));
例子3-流API
使用回调API:
import fs from 'node:fs';
const readStream = fs.createReadStream('mytest.mjs', 'utf-8');
readStream.on('data', (chunk) => {
console.log('Read data:', chunk);
});
readStream.on('end', () => {
console.log('Finished reading file');
});
使用promises API:
import fsPromises from 'fs/promises';
const fd = await fsPromises.open('mytest.mjs', 'r');
const stream = fd.createReadStream();
stream.on('data', (chunk) => {
console.log(chunk.toString());
});
参考
- https://nodejs.org/api/fs.html