一、实用小Demo
1.图片上传
// 1. 判断服务器上面有没有 upload 目录,没有就创建这个目录 // 2. 找出 html 目录下面的所有的目录,然后打印出来 const fs = require('fs'); fs.stat('upload', (err, stats) => { // 判断有没有 upload 目录 if(err) { // 如果没有 fs.mkdir('upload', (error) => { if(error) { console.log(error); return false; } else { console.log("创建 upload 目录成功!"); } }) } else { // 如果有 console.log(stats.isDirectory()); console.log("有 upload 目录,你可以做更多操作!"); } })
2.读取目录全部文件
const fs = require('fs'); fs.readdir('node_modules', (err, files) => { if(err) { console.log(err); return false; } else { // 判断是目录还是文件夹 console.log(files); let filesArr = []; (function getFile(i) { // 循环结束 if(i == files.length) { // 打印出所有目录 console.log("目录:"); console.log(filesArr); return false; } // 判断目录是文件还是文件夹 fs.stat('node_modules/' + files[i], (error, stats) => { if(stats.isDirectory()) { filesArr.push(files[i]); } // 递归调用 getFile(i+1); }) })(0) } })
二、实现递归创建目录
我们创建一个函数,参数为一个路径,按照路径一级一级的创建文件夹目录。
1、同步的实现
const fs = require("fs"); const path = require("path"); // 同步创建文件目录 function mkPathSync(dirPath) { // path.sep 文件路径分隔符(mac 与 window 不同) // 转变成数组,如 ['a', 'b', 'c'] let parts = dirPath.split(path.sep); for(let i = 1; i <= parts.length; i++) { // 重新拼接成 a a/b a/b/c let current = parts.slice(0, i).join(path.sep); // accessSync 路径不存在则抛出错误在 catch 中创建文件夹 try { fs.accessSync(current); } catch(e) { fs.mkdirSync(current); } } } // 创建文件目录 mkPathSync(path.join("a", "b", "c"));
同步代码就是利用 accessSync
方法检查文件路径是否存在,利用 try...catch...
进行错误捕获,如果路径不存在,则会报错,会进入 catch
完成文件夹的创建。
2、异步回调的实现
const fs = require("fs"); const path = require("path"); function mkPathAsync(dirPath, callback) { // 转变成数组,如 ['a', 'b', 'c'] let parts = dirPath.split(path.sep); let index = 1; // 创建文件夹方法 function next() { // 重新拼接成 a a/b a/b/c let current = parts.slice(0, index).join(path.sep); index++; // 如果路径检查成功说明已经有该文件目录,则继续创建下一级 // 失败则创建目录,成功后递归 next 创建下一级 fs.access(current, err => { if (err) { fs.mkdir(current, next); } else { next(); } }); } next(); } // 创建文件目录 mkPathAsync(path.join("a", "b", "c"), () => { console.log("创建文件目录完成") }); // 创建文件目录完成
上面方法中没有通过循环实现每次目录的拼接,而是通过递归内部函数 next
的方式并维护 index
变量来实现的,在使用 access
的时候成功说明文件目录已经存在,就继续递归创建下一级,如果存在 err
说明不存在,则创建文件夹。
3、异步 async/await 的实现
上面两种方式,同步阻塞代码,性能不好,异步回调函数嵌套性能好,但是维护性差,我们想要具备性能好,代码可读性又好可以使用现在 NodeJS 中正流行的 async/await
的方式进行异步编程,想了解 async/await
可以看 异步发展流程 —— 异步编程的终极大招 async/await 这篇文章。
使用 async
函数中 await
等待的异步操作必须转换成 Promise,以前我们都使用 util
模块下的 promisify
方法进行转换,其实 promisify
方法的原理很简单,我们在实现递归创建文件目录之前先实现 promisify
方法。
// 将一个异步方法转换成 Promise function promisify(fn) { return function (...args) { return new Promise((resolve, reject) => { fn.call(null, ...args, err => err ? reject() : resolve()); }); } }
其实 promisify
方法就是利用闭包来实现的,调用时传入一个需要转换成 Promise 的函数 fn
,返回一个闭包函数,在闭包函数中返回一个 Promise 实例,并同步执行了 fn
,通过 call
将闭包函数中的参数和回调函数作为参数传入了 fn
中,该回调在存在错误的时候调用了 Promise 实例的 reject
,否则调用 resolve
;
const fs = require("fs"); const path = require("path"); // 将 fs 中用到的方法转换成 Promise const access = promisify(fs.access); const mkdir = promisify(fs.mkdir); // async/await 实现递归创建文件目录 async function mkPath(dirPath) { // 转变成数组,如 ['a', 'b', 'c'] let parts = dirPath.split(path.sep); for(let i = 1; i <= parts.length; i++) { // 重新拼接成 a a/b a/b/c let current = parts.slice(0, i).join(path.sep); // accessSync 路径不存在则抛出错误在 catch 中创建文件夹 try { await access(current); } catch(e) { await mkdir(current); } } } // 创建文件目录 mkPath(path.("a", "b", "c")).then(() => { console.log("创建文件目录完成"); }); // 创建文件目录完成
使用 async/await
的写法,代码更像同步的实现方式,却是异步执行,所以同时兼顾了性能和代码的可读性,优势显而易见,在使用 NodeJS 框架 Koa 2.x
版本时大量使用这种方式进行异步编程。