Javascript 递归爬网并以异步方式写入文件

Javascript 递归爬网并以异步方式写入文件,javascript,node.js,asynchronous,async-await,fs,Javascript,Node.js,Asynchronous,Async Await,Fs,我对异步代码是完全陌生的,所以我现在有点不知所措 我所做的是递归地异步抓取归档文件,以便检测给定归档文件中的文件路径。我要做的是,当检测到所有文件路径时,将它们写入单个文件。但是,当我执行代码时,它不会正确地将它们写入文件。我假设这是由于多个写操作同时发生 data.json之前 data.json之后 显然,我可以同步编写代码,但为了提高效率,我更愿意异步编写所有这些代码。尽管如此,我真的不确定最好的方法。我知道这样做的一种方法是等待最后一个文件被爬网和推送(然后将新属性写入文件),但我不知道

我对异步代码是完全陌生的,所以我现在有点不知所措

我所做的是递归地异步抓取归档文件,以便检测给定归档文件中的文件路径。我要做的是,当检测到所有文件路径时,将它们写入单个文件。但是,当我执行代码时,它不会正确地将它们写入文件。我假设这是由于多个写操作同时发生

data.json之前 data.json之后 显然,我可以同步编写代码,但为了提高效率,我更愿意异步编写所有这些代码。尽管如此,我真的不确定最好的方法。我知道这样做的一种方法是等待最后一个文件被爬网和推送(然后将新属性写入文件),但我不知道如何在异步环境中有效地检测它?我可以经常检查,但在我看来这是一个愚蠢的方法

下面是导致问题的异步代码

// Scan directories looking for target file types.
async function scanDirs(){
  const
    config = await fsp.readFile('./config.json', 'utf8'),
    archives = JSON.parse(config).archives,
    { join } = require('path'),
    traverse = async (path) => {
      try {
        const stats = await fsp.stat(path)
        if (stats.isDirectory()){
          const childPaths = await fsp.readdir(path)
          for (const childPath of childPaths){
            const
              fullPath = join(path, childPath)
            traverse(fullPath)
          }
        } else if (stats.isFile()) {
          const
            fileTypes = config.fileTypes,
            fileExt = path.substring(path.lastIndexOf('.')+1)
          if (fileTypes.includes(fileExt)){
            const
              data = await fsp.readFile('./data.json', 'utf8'),
              json = JSON.parse(data),
              drive = path.substring(0,1),
              files = json[drive].files,
              stat = await fsp.stat(path),
              newFile = {
                "path": path,
                "name": path.substring(path.lastIndexOf('\\')+1),
                "bytes": stat.size
              }
            files.push(newFile)
            fsp.writeFile('./data.json', JSON.stringify(json, null, 2))
          }
        }
      }
      catch (error){
        console.error(error)
      }
    }

  for (const path of archives){
    traverse(path)
  }
}
任何帮助都将不胜感激

我知道这样做的一种方法是等到最后一个文件被爬网后再写,但我不知道如何在异步环境中有效地检测它

您将使用等待多个承诺:

const { join } = require('path');
async function searchFiles(path, fileTypes) {
  try {
    const stats = await fsp.stat(path)
    if (stats.isDirectory()){
      const childPaths = await fsp.readdir(path)
      const promises = childPaths.map(childPath =>
        searchFiles(join(path, childPath), fileTypes)
      );
      const results = await Promise.all(promises);
      return [].concat(...results);
    } else if (stats.isFile()) {
      const fileExt = path.substring(path.lastIndexOf('.')+1)
      if (fileTypes.includes(fileExt)) {
        return [{
          "path": path,
          "name": path.substring(path.lastIndexOf('\\')+1),
          "bytes": stats.size
        }];
      }
    }
  } catch(e) {
    // ignore. Log?
  }
  return [];
}
async function readJson(path) {
  return JSON.parse(await fsp.readFile(path, 'utf8'));
}

// Scan directories looking for target file types.
async function scanDirs() {
  try {
    const [config, data] = await Promise.all([readJson('./config.json'), readJson('./data.json')]);
    const results = await Promise.all(config.archives.map(path => searchFiles(path, config.fileTypes)));
    for (const newFile of [].concat(...results)) {
      const drive = newFile.path.substring(0,1);
      data[drive].files.push(newFile);
    }
    fsp.writeFile('./data.json', JSON.stringify(data, null, 2));
  } catch (error){
    console.error(error)
  }
}

BTW,您可能需要考虑从字符串操作中使用<代码> BaseNe< /COD>和<代码> ExtNeX/COD>,但鉴于这只是一个Windows程序(使用驱动器字母),这可能没什么关系。

在您的函数中递归地构建数据结构,将其作为一个对象(作为一个允诺)返回。然后等待,序列化它,只将它写入文件一次。你说的“我不关心结构”是什么意思?肯定是乱码输出不好吗?@Bergi所有im记录的都是我粘贴到文件中的文件路径array@Bergi我很困惑如何返回
遍历
作为承诺。im从
for
循环调用
遍历
的次数不定,而循环本身调用的次数不定。我对承诺和异步代码是全新的,所以请像我5岁时那样给我解释一下,我现在就要玩这个,这样我就可以理解发生了什么。感谢您的帮助所以基本上任何异步函数都是一个承诺,对吗?另外,当我从另一个函数调用
readJson('path')
时,在新上下文中
readJson
是一个等待的承诺吗?例如,
async someOtherFunction(){const c=readJson('./config.json'),d=c.archives}
d
将始终在此上下文中等待
c
,或者无论上下文是什么?@PrimitiveNom Every
async
异步函数都会返回一个承诺,是。@PrimitiveNom上下文不会等待,直到您显式地等待承诺。不,示例中的
d
不会等待
c
,它不知道或不关心
readJson
做什么。“等待上下文”只是当前的
异步函数
,它的执行被暂停(就像生成器一样),直到当前等待的承诺得到解决。其他函数中的其他代码不受此影响。
// Scan directories looking for target file types.
async function scanDirs(){
  const
    config = await fsp.readFile('./config.json', 'utf8'),
    archives = JSON.parse(config).archives,
    { join } = require('path'),
    traverse = async (path) => {
      try {
        const stats = await fsp.stat(path)
        if (stats.isDirectory()){
          const childPaths = await fsp.readdir(path)
          for (const childPath of childPaths){
            const
              fullPath = join(path, childPath)
            traverse(fullPath)
          }
        } else if (stats.isFile()) {
          const
            fileTypes = config.fileTypes,
            fileExt = path.substring(path.lastIndexOf('.')+1)
          if (fileTypes.includes(fileExt)){
            const
              data = await fsp.readFile('./data.json', 'utf8'),
              json = JSON.parse(data),
              drive = path.substring(0,1),
              files = json[drive].files,
              stat = await fsp.stat(path),
              newFile = {
                "path": path,
                "name": path.substring(path.lastIndexOf('\\')+1),
                "bytes": stat.size
              }
            files.push(newFile)
            fsp.writeFile('./data.json', JSON.stringify(json, null, 2))
          }
        }
      }
      catch (error){
        console.error(error)
      }
    }

  for (const path of archives){
    traverse(path)
  }
}
const { join } = require('path');
async function searchFiles(path, fileTypes) {
  try {
    const stats = await fsp.stat(path)
    if (stats.isDirectory()){
      const childPaths = await fsp.readdir(path)
      const promises = childPaths.map(childPath =>
        searchFiles(join(path, childPath), fileTypes)
      );
      const results = await Promise.all(promises);
      return [].concat(...results);
    } else if (stats.isFile()) {
      const fileExt = path.substring(path.lastIndexOf('.')+1)
      if (fileTypes.includes(fileExt)) {
        return [{
          "path": path,
          "name": path.substring(path.lastIndexOf('\\')+1),
          "bytes": stats.size
        }];
      }
    }
  } catch(e) {
    // ignore. Log?
  }
  return [];
}
async function readJson(path) {
  return JSON.parse(await fsp.readFile(path, 'utf8'));
}

// Scan directories looking for target file types.
async function scanDirs() {
  try {
    const [config, data] = await Promise.all([readJson('./config.json'), readJson('./data.json')]);
    const results = await Promise.all(config.archives.map(path => searchFiles(path, config.fileTypes)));
    for (const newFile of [].concat(...results)) {
      const drive = newFile.path.substring(0,1);
      data[drive].files.push(newFile);
    }
    fsp.writeFile('./data.json', JSON.stringify(data, null, 2));
  } catch (error){
    console.error(error)
  }
}