Zijue / blog

personal knowledge collection

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

10.文件夹删除的方式:异步串行与并发

Zijue opened this issue · comments

commented

文件夹删除思路

假设现在有一个文件夹,目录树结构如下:

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);
})