Javascript 如何一次处理多个承诺
我正在创建一个程序 一,。检测任何给定系统上的所有驱动器 二,。扫描这些驱动器以查找特定文件类型的文件。例如,Javascript 如何一次处理多个承诺,javascript,node.js,asynchronous,promise,fs,Javascript,Node.js,Asynchronous,Promise,Fs,我正在创建一个程序 一,。检测任何给定系统上的所有驱动器 二,。扫描这些驱动器以查找特定文件类型的文件。例如,它可以在所有驱动器中搜索任何jpeg、png和svg文件 三,。然后,结果以以下所需格式存储在JSON文件中 { "C:": { "jpeg": [ ... { "path": "C:\\Users\\John\\Pictures\\example.jpeg", "name": "example", "t
它可以在所有驱动器中搜索任何
jpeg
、png
和svg
文件
三,。然后,结果以以下所需格式存储在JSON文件中
{
"C:": {
"jpeg": [
...
{
"path": "C:\\Users\\John\\Pictures\\example.jpeg",
"name": "example",
"type": "jpeg",
"size": 86016
},
...
],
"png": [],
"svg": []
},
...
}
代码
async function scan(path, exts) {
try {
const stats = await fsp.stat(path)
if (stats.isDirectory()) {
const
childPaths = await fsp.readdir(path),
promises = childPaths.map(
childPath => scan(join(path, childPath), exts)
),
results = await Promise.all(promises)
// Likely needs to change.
return [].concat(...results)
} else if (stats.isFile()) {
const fileExt = extname(path).replace('.', '')
if (exts.includes(fileExt)){
// Likely needs to change.
return {
"path": path,
"name": basename(path, fileExt).slice(0, -1),
"type": fileExt,
"size": stats.size
}
}
}
return []
}
catch (error) {
return []
}
}
const results = await Promise.all(
config.drives.map(drive => scan(drive, exts))
)
console.log(results) // [ Array(140), Array(0), ... ]
// And I would like to do something like the following...
for (const drive of results) {
const
root = parse(path).root,
fileExt = extname(path).replace('.', '')
data[root][fileExt] = []
}
await fsp.writeFile('./data.json', JSON.stringify(config, null, 2))
全局结果
当然被划分为与每个驱动器相对应的单个阵列。但目前它将所有对象合并到一个巨大的数组中,尽管它们的文件类型不同。目前,我也无法知道哪个阵列属于每个驱动器,特别是如果驱动器的阵列不包含任何我可以解析以检索根目录的项目
显然,我可以映射
或再次循环全局结果
,然后对所有内容进行排序,如下图所示,但是让扫描()
从一开始就处理所有内容会更干净
// Initiate scan sequence.
async function initiateScan(exts) {
let
[config, data] = await Promise.all([
readJson('./config.json'),
readJson('./data.json')
]),
results = await Promise.all(
// config.drives.map(drive => scan(drive, exts))
['K:', 'D:'].map(drive => scan(drive, exts))
)
for (const drive of results) {
let root = false
for (const [i, file] of drive.entries()) {
if (!root) root = parse(file.path).root.slice(0,-1)
if (!data[root][file.type] || !i) data[root][file.type] = []
data[root][file.type].push(file)
}
}
await fsp.writeFile('./data.json', JSON.stringify(config, null, 2))
}
由于我缺乏异步性和一般对象方面的经验,我不太确定如何最好地处理map(…)
/scan
中的数据。我甚至不知道如何最好地构造scan()
的输出,从而使全局结果的结构易于操作
任何帮助都将不胜感激。这仍然需要一些工作,它正在第二次迭代生成的文件集合
//这将为您获得一个每个驱动器具有一个属性的对象
const results=Object.fromEntries(
(等待承诺(
config.drives.map(异步驱动器=>[驱动器,等待扫描(驱动器,exts)])
)
)
.地图(
([驱动器,文件])=>[
驾驶,
//我们将每个驱动器的文件数组缩减为一个具有
//每个文件扩展名一个属性
文件.reduce(
(acc,文件)=>{
acc[file.type].推送(文件)
返回acc
},
Object.fromEntries(exts.map(ext=>[ext,[]))
)
]
)
)
nodejs支持12.0.0版,因此,如果您能保证应用程序始终在该版本或更高版本中运行,对象。fromEntries
在这里应该很好。您可以使用获取所有文件名,然后将该数组转换为您的对象,如下所示:
import {basename, extname} from 'path';
import {stat} from 'fs/promises'; // Or whichever library you use to promisify fs
import * as glob from "glob";
function searchForFiles() {
return new Promise((resolve, reject) => glob(
"/**/*.{jpeg,jpg,png,svg}", // The files to search for and where
{ silent: true, strict: false}, // No error when eg. something cannot be accessed
(err, files) => err ? reject() : resolve(files)
));
}
async function getFileObject() {
const fileNames = await searchForFiles(); // An array containing all file names (eg. ['D:\\my\path\to\file.jpeg', 'C:\\otherfile.svg'])
// An array containing all objects describing your file
const fileObjects = await Promise.all(fileNames.map(async filename => ({
path: filename,
name: basename(path, fileExt).slice(0, -1),
type: extname(path).replace('.', ''),
size: stat(path).size,
drive: `${filename.split(':\\')[0]}:`
})));
// Create your actual object
return fileObjects.reduce((result, {path, name, type, size, drive}) => {
if (!result[drive]) { // create eg. { C: {} } if it does not already exist
result.drive = {};
}
if (!result[drive][type]) { // create eg. {C: { jpeg: [] }} if it does not already exist
result[drive][type] = [];
}
// Push the object to the correct array
result[drive][type].push({path, name, type, size});
return result;
}, {});
}
函数必须递归遍历文件系统,查找符合条件的文件。递归可以简化,因为结果不需要保留任何层次结构,所以我们可以将平面数组(文件
)作为参数
let exts = [...]
async function scan(path, files) {
const stats = await fsp.stat(path)
if (stats.isDirectory()) {
childPaths = await fsp.readdir(path)
let promises = childPaths.map(childPath => {
return scan(join(path, childPath), files)
})
return Promise.all(promises)
} else if (stats.isFile()) {
const fileExt = extname(path).replace('.', '')
if (exts.includes(fileExt)) {
files.push({
path: path,
name: basename(path, fileExt).slice(0, -1),
type: fileExt,
size: stats.size
})
}
}
}
let files = []
await scan('/', files)
console.log(files)
当异步派生的结果到达时,对外部对象进行变异并不是特别干净,但是它可以相当简单和安全地完成,如下所示:
(async function(exts, results) { // async IIFE wrapper
async function scan(path) { // lightly modified version of scan() from the question.
try {
const stats = await fsp.stat(path);
if (stats.isDirectory()) {
const childPaths = await fsp.readdir(path);
const promises = childPaths.map(childPath => scan(join(path, childPath)));
return Promise.all(promises);
} else if (stats.isFile()) {
const fileExt = extname(path).replace('.', '');
if (results[path] && results[path][fileExt]) {
results[path][fileExt].push({
'path': path,
'name': basename(path, fileExt).slice(0, -1),
'type': fileExt,
'size': stats.size
});
}
}
}
catch (error) {
console.log(error);
// swallow error by not rethrowing
}
}
await Promise.all(config.drives.map(path => {
// Synchronously seed the results object with the required data structure
results[path] = {};
for (fileExt of exts) {
results[path][fileExt] = []; // array will populated with data, or remain empty if no qualifying data is found.
}
// Asynchronously populate the results[path] object, and return Promise to the .map() callback
return scan(path);
}));
console.log(results);
// Here: whatever else you want to do with the results.
})(exts, {}); // pass `exts` and an empty results object to the IIFE function.
结果对象与空数据结构同步播种,然后异步填充
所有内容都包装在异步立即调用函数表达式(IIFE)中,因此:
- 避免全局命名空间(如果尚未避免)
- 确保
的可用性等待(如果尚未可用)
- 对
结果
对象进行安全关闭
这更接近我想要的。这很难,因为有多个层。我的初始代码的示例输出是[Array(167),Array(0)…]
,每个数组对应一个驱动器。“结果然后存储在一个JSON文件中,格式如下…”。你是说结果应该以这种方式存储吗?@Roamer-1888是的,这就是我的意思。这就是我想要存储它们的方式问题是,我使用exec('wmic logicaldisk get name')
检测并扫描给定系统上的所有驱动器。我希望能够将结果存储在一个json文件中,该文件的结构如问题开头所述,如果scan()
只是返回一个表示每个文件的对象平面数组,那么我必须循环遍历整个数组并逐个解析对象,这似乎效率低下。这有意义吗?也许我最初的问题措辞不太好我希望扫描所有的驱动器,并在这个过程中对它们进行排序,以避免多次循环通过“相同”的数据/输出我想你的答案会起作用,但似乎效率极低。为什么循环通过“相同”数据/输出3次??在scan()
中处理所有排序等,这样输出就已经是所需的格式了,这样就不必再循环2次了,这样不是更好吗?您希望数组有多大?考虑到目前为止最慢的部分是对底层文件系统的访问,循环本身的开销是可以忽略的。即使您的阵列有一百万个条目,一个循环也只需几分之一秒。我不是100%确定,但每个驱动器可能有几万个文件。我自己的电脑上有9个驱动器。我知道仅仅通过阵列循环不会占用很多时间,但是如果我也解析其中的每个对象,那么循环就完全不需要担心了。我只是仔细检查了一下:即使你在浏览器中运行这些循环(显然没有文件系统访问权限)有一百万个条目,每个循环大约需要200毫秒,包括创建对象。此外,你也不会遍历相同的数据3次。glob库执行的第一个“循环”是遍历整个文件系统。这很容易就涉及到几十亿个条目。第二个和第三个循环仅针对其中的一个子集运行—所有与搜索条件匹配的文件。相比之下,第二个和第三个循环就显得苍白无力了。我通常会否决一个建议在map
这样的操作中变异外部对象的答案,但这是一种巧妙的方法,可以绕过Promise的限制。所有这些都是为了产生欲望