10.文件夹删除的方式:异步串行与并发
Zijue opened this issue · comments
文件夹删除思路
假设现在有一个文件夹,目录树结构如下:
a
├── b
│ └── e
├── c
│ └── f
└── d
├── g
└── h
首先最容易想到的就是串行的方式:遇到文件夹先去遍历子节点,当子节点删除完毕后再删除自己,如果子节点也有子节点,则递归
如上图所示,将整个的删除过程串联起来,这也称之为:异步串行。异步串行
深度优先
上面介绍的处理文件夹删除的**就是异步串行-深度优先**,具体实现代码如下:
const fs = require('fs');
const path = require('path');
function myRmdir(dir, callback) {
fs.stat(dir, (err, statObj) => {
if (statObj.isFile()) {
fs.unlink(dir, callback); // 如果是文件直接删除即可
} else {
fs.readdir(dir, (err, dirs) => {
if (dirs.length != 0) {
// 1.读取所有的子节点路径
dirs = dirs.map(d => path.join(dir, d));
}
// 2.依次拿出子节点进行删除操作
let idx = 0;
function next() {
// 3.当前节点索引与子节长度相同时,表示子节点已删除完毕
if (idx == dirs.length) return fs.rmdir(dir, callback);
let current = dirs[idx++];
myRmdir(current, next);
}
next();
})
}
})
}
myRmdir('a1', () => {
console.log('删除完成');
});
广度优先
异步串行的方式除了深度优先,还有广度优先的**:先采用异步的方式读取目录,维护想要的文件目录关系,最终将结果倒序删除
**如下图所示(先维护关系再倒叙删除):
之后以此内推,将指针指向`e`、`f`、`g`、`h`直到数组遍历完成。代码实现如下:const fs = require('fs');
const path = require('path');
function myRmdir(dir, callback) {
stack = [dir];
function reverseRemove() {
let idx = stack.length - 1;
function next() {
if (idx < 0) return callback();
let current = stack[idx--];
fs.rmdir(current, next);
}
next();
}
fs.stat(dir, (err, statObj) => {
if (statObj.isFile()) {
fs.unlink(dir, callback); // 如果是文件直接删除即可
} else {
// 如果是目录,采用广度遍历的方式
// 采用异步的方式读取目录,维护想要的结果,最终将结果倒序删除
let idx = 0;
function next() {
let dir = stack[idx++];
if (!dir) return reverseRemove();
fs.readdir(dir, (err, dirs) => {
if (dirs.length != 0) {
dirs = dirs.map(d => path.join(dir, d));
}
stack.push(...dirs);
next();
})
}
next();
}
})
}
myRmdir('a1', () => {
console.log('删除完成');
});
并发删除
异步串行的方式,明显性能不太高效,需要提高效率则采用并发删除的方式,代码如下:
const fs = require('fs');
const path = require('path');
function myRmdir(dir, callback) {
fs.stat(dir, (err, statObj) => {
if (statObj.isFile()) {
fs.unlink(dir, callback); // 如果是文件直接删除即可
} else {
// 如果是文件夹,同时删除子节点(如果子节点为空则需要删除自己)
fs.readdir(dir, (err, dirs) => {
if (dirs.length != 0) {
dirs = dirs.map(d => path.join(dir, d));
} else {
return fs.rmdir(dir, callback); // 没有子节点,删除自身
}
let idx = 0;
function removeCount() {
if (++idx == dirs.length) {
fs.rmdir(dir, callback);
}
}
dirs.forEach(dir => {
myRmdir(dir, removeCount);
})
})
}
})
}
myRmdir('a1', () => {
console.log('删除完成');
});
核心**就是,删除子节点成功时,回调计数器函数,当计数器的值与子节点长度相等时,删除父节点
promise优化
上述并发删除的方式,虽然性能会有提升,但是回调的方式不够优化,采用promise
的方法进行优化
const fs = require('fs');
const path = require('path');
function myRmdir(dir) {
return new Promise((resolve, reject) => {
fs.stat(dir, (err, statObj) => {
if (err) reject(err);
if (statObj.isFile()) {
fs.unlink(dir, resolve);
} else {
fs.readdir(dir, (err, dirs) => {
if (err) reject(err);
// map 返回的是删除子节点列表的promise数据
if (dirs.length != 0) {
dirs = dirs.map(d => myRmdir(path.join(dir, d)));
}
Promise.all(dirs).then(() => {
fs.rmdir(dir, resolve);
}).catch(err => {
reject(err);
})
})
}
})
})
}
myRmdir('a1').then(() => {
console.log('删除成功');
}).catch(err => {
console.log('删除失败', err);
})
async + await
使用promise优化过后,代码简洁不少,但是还不太够,使用async+await方式进一步优化,将异步操作写的像同步一样
const fs = require('fs').promises;
const path = require('path');
async function myRmdir(dir) {
let statObj = await fs.stat(dir); // statObj | 不存在则报错
if (statObj.isDirectory()) {
let dirs = await fs.readdir(dir); // 返回的是一个数组
// 使用Promise.all将所有子文件包裹起来进行删除
await Promise.all(dirs.map(d => myRmdir(path.join(dir, d))));
await fs.rmdir(dir);
} else {
await fs.unlink(dir);
}
}
myRmdir('a1').then(() => {
console.log('删除成功');
}).catch(err => {
console.log('删除失败', err);
})