Bra*_*eld 1 asynchronous fs node.js promise
在文档中,它显示了readdir和stat的两个版本。两者都有async和sync版本readir/readdirSync和stat/statSync。
因为readidir和stat是异步的,我希望他们返回一个Promise,但是当尝试使用async/await该脚本时,它不会等待readdir解决,如果我使用的话,.then/.catch我会得到一个错误cannot read .then of undefined。
我在这里要做的就是将脚本正在其中运行的目录中存在的目录dirsOfCurrentDir映射到该地图。
cannot read .then of undefinedconst fs = require('fs');
const directory = `${ __dirname }/${ process.argv[2] }`;
const dirsOfCurrentDir = new Map();
fs.readdir(directory, (err, files) => {
let path;
if (err)
return console.log(err);
files.forEach(file => {
path = directory + file;
fs.stat(path, (err, stats) => {
if (err)
return console.log(err);
dirsOfCurrentDir.set(file, directory);
});
});
}).then(() => console.log('adasdasd'))
console.log(dirsOfCurrentDir)
Run Code Online (Sandbox Code Playgroud)
Map {}const foo = async () => {
await fs.readdir(directory, (err, files) => {
let path;
if (err)
return console.log(err);
files.forEach(file => {
path = directory + file;
fs.stat(path, (err, stats) => {
if (err)
return console.log(err);
dirsOfCurrentDir.set(file, directory);
});
});
});
};
foo()
console.log(dirsOfCurrentDir)
Run Code Online (Sandbox Code Playgroud)
我最终选择了这两个功能readdirSync和的同步版本statSync。虽然使用async方法或Promisify会感觉更好,但我仍然没有弄清楚如何使用这两种方法使我的代码正常工作。
const fs = require('fs');
const directory = `${ __dirname }/${ process.argv[2] }`;
const dirsOfCurrentDir = new Map();
const dirContents = fs.readdirSync(directory);
dirContents.forEach(file => {
const path = directory + file;
const stats = fs.statSync(path);
if (stats.isDirectory())
dirsOfCurrentDir.set(file, path);
});
console.log(dirsOfCurrentDir); // logs out the map with all properties set
Run Code Online (Sandbox Code Playgroud)
因为readidir和stat是异步的,所以我希望它们返回Promise
首先,请确保您知道异步函数和async函数之间的区别。async在Javascript中声明为使用该特定关键字的函数,例如:
async function foo() {
...
}
Run Code Online (Sandbox Code Playgroud)
确实会返回一个promise(根据用async关键字声明的函数的定义)。
但是异步函数(例如,fs.readdir()可能会或可能不会返回承诺)取决于其内部设计。在这种情况下,fsnode.js中模块的原始实现仅使用回调,而不使用诺言(其设计早于node.js中存在诺言)。它的功能是异步的,但未声明为async,因此使用常规回调而不是promise。
因此,您必须使用回调或“ promisify”接口将其转换为可返回promise的东西,以便您可以使用await它。
node.js v10中有一个实验性接口,可为fs模块提供内置的Promise。
const fsp = require('fs').promises;
fsp.readdir(...).then(...)
Run Code Online (Sandbox Code Playgroud)
在早期版本的node.js中,有很多选项可用于分散功能。您可以使用util.promisify()来实现该功能:
const promisify = require('util').promisify;
const readdirP = promisify(fs.readdir);
const statP = promisify(fs.stat);
Run Code Online (Sandbox Code Playgroud)
由于我尚未在节点v10上进行开发,因此我经常使用Bluebird Promise库并一次使整个fs库成倍增加:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
fs.readdirAsync(...).then(...)
Run Code Online (Sandbox Code Playgroud)
要仅列出给定目录中的子目录,可以执行以下操作:
const fs = require('fs');
const path = require('path');
const promisify = require('util').promisify;
const readdirP = promisify(fs.readdir);
const statP = promisify(fs.stat);
const root = path.join(__dirname, process.argv[2]);
// utility function for sequencing through an array asynchronously
function sequence(arr, fn) {
return arr.reduce((p, item) => {
return p.then(() => {
return fn(item);
});
}, Promise.resolve());
}
function listDirs(rootDir) {
const dirsOfCurrentDir = new Map();
return readdirP(rootDir).then(files => {
return sequence(files, f => {
let fullPath = path.join(rootDir, f);
return statP(fullPath).then(stats => {
if (stats.isDirectory()) {
dirsOfCurrentDir.set(f, rootDir)
}
});
});
}).then(() => {
return dirsOfCurrentDir;
});
}
listDirs(root).then(m => {
for (let [f, dir] of m) {
console.log(f);
}
});
Run Code Online (Sandbox Code Playgroud)
这是一个更通用的实现,它列出文件并为列出内容和显示结果的方式提供几种选择:
const fs = require('fs');
const path = require('path');
const promisify = require('util').promisify;
const readdirP = promisify(fs.readdir);
const statP = promisify(fs.stat);
const root = path.join(__dirname, process.argv[2]);
// options takes the following:
// recurse: true | false - set to true if you want to recurse into directories (default false)
// includeDirs: true | false - set to true if you want directory names in the array of results
// sort: true | false - set to true if you want filenames sorted in alpha order
// results: can have any one of the following values
// "arrayOfFilePaths" - return an array of full file path strings for files only (no directories included in results)
// "arrayOfObjects" - return an array of objects {filename: "foo.html", rootdir: "//root/whatever", full: "//root/whatever/foo.html"}
// results are breadth first
// utility function for sequencing through an array asynchronously
function sequence(arr, fn) {
return arr.reduce((p, item) => {
return p.then(() => {
return fn(item);
});
}, Promise.resolve());
}
function listFiles(rootDir, opts = {}, results = []) {
let options = Object.assign({recurse: false, results: "arrayOfFilePaths", includeDirs: false, sort: false}, opts);
function runFiles(rootDir, options, results) {
return readdirP(rootDir).then(files => {
let localDirs = [];
if (options.sort) {
files.sort();
}
return sequence(files, fname => {
let fullPath = path.join(rootDir, fname);
return statP(fullPath).then(stats => {
// if directory, save it until after the files so the resulting array is breadth first
if (stats.isDirectory()) {
localDirs.push({name: fname, root: rootDir, full: fullPath, isDir: true});
} else {
results.push({name: fname, root: rootDir, full: fullPath, isDir: false});
}
});
}).then(() => {
// now process directories
if (options.recurse) {
return sequence(localDirs, obj => {
// add directory to results in place right before its files
if (options.includeDirs) {
results.push(obj);
}
return runFiles(obj.full, options, results);
});
} else {
// add directories to the results (after all files)
if (options.includeDirs) {
results.push(...localDirs);
}
}
});
});
}
return runFiles(rootDir, options, results).then(() => {
// post process results based on options
if (options.results === "arrayOfFilePaths") {
return results.map(item => item.full);
} else {
return results;
}
});
}
// get flat array of file paths,
// recursing into directories,
// each directory sorted separately
listFiles(root, {recurse: true, results: "arrayOfFilePaths", sort: true, includeDirs: false}).then(list => {
for (const f of list) {
console.log(f);
}
}).catch(err => {
console.log(err);
});
Run Code Online (Sandbox Code Playgroud)
您可以将此代码复制到文件中并运行它,并.作为参数传递以列出脚本目录或要列出的任何子目录名称。
如果您想要更少的选项(例如不保留递归或不保留目录顺序),则可以大大减少此代码,甚至可以使其速度更快(并行运行一些异步操作)。