基于递归 Promise 的目录读取

Posted

技术标签:

【中文标题】基于递归 Promise 的目录读取【英文标题】:Recursive Promise-based directory reading 【发布时间】:2018-08-12 12:07:07 【问题描述】:

我有一个库,可以扫描远程服务器上的文件目录。它返回一个这样的 Promise:

client.scanRemoteDirectory(path)
  .then(files =>  

    console.log(files)

  )

我也在尝试编写一个递归方法来扫描目录和子目录。但我遇到了一些异步问题。我的功能是这样的:

const scanDir(path) 

  // Scan the remote directory for files and sub-directories
  return client.scanRemoteDirectory(path)
    .then(files => 

      for (const file of files) 
        // If a sub-directory is found, scan it too
        if (file.type === 'directory') 

          return scanDir(file.path) // Recursive call

        
      
    )


const scanDir('some/path')
  .then(() => 
    console.log('done')
  )

但是,由于scanDir() 递归方法调用前面的return,这会导致该方法仅扫描每个目录中的第一个子目录并跳过其余子目录。

例如,如果结构是这样的:

/some/path
/some/path/dirA
/some/path/dirA/subdirA
/some/path/dirB
/some/path/dirB/subdirB

上述方法只会扫描:

/some/path
/some/path/dirA
/some/path/subdirA

它将跳过 dirB 和它的孩子,因为该方法首先找到 dirA

如果我只是从return scanDir(...) 调用中删除return,那么它会很好地扫描所有内容。但是我最后的console.log('done') 发生得太快了,因为它是异步的。

那么我该如何解决这个问题呢?什么是正确的递归 Promise 方法,我仍然可以保留异步但还可以递归扫描每个子目录?

【问题讨论】:

就个人而言,我会使用 promise.all 而不是 foreach 循环来解决这个问题。简而言之,创建一个承诺数组,循环“文件”数组,将每个承诺推送到数组并调用promise.all。这样,无论有多少个promise被递归链接,父promise只有在children有的时候才会被resolve。另外,我会手动处理链顶部的Promise 对象。总结一下,创建数组,推送承诺并执行return Promise.all(array)。但请注意,因为响应会略有不同。 ***.com/search?q=[promise]%20recursive%20directory 感谢 Promise.all 的来电。返回 Promise.all 而不是 Promise 效果很好。谢谢, 【参考方案1】:

您可能希望在这种情况下使用Promise.all 来并行运行您的“子”承诺,例如:

function scanDir(path) 

    return client.scanRemoteDirectory(path)
        .then(all => 
            const files = all.where(file => file.type !== 'directory);
            const dirs = all.where(file => file.type === 'directory);
            return Promise.all(dirs.map(dir => scanDir(dir.path)) // Execute all 'sub' promises in parallel.
                .then(subFiles => 
                    return files.concat(subFiles);
                );
        );

或者,您可以使用reduce 函数按顺序运行您的“子”承诺:

function scanDir(path) 

    return client.scanRemoteDirectory(path)
        .then(all => 
            const files = all.where(file => file.type !== 'directory);
            const dirs = all.where(file => file.type === 'directory);
            return dirs.reduce((prevPromise, dir) =>  // Execute all 'sub' promises in sequence.
                    return prevPromise.then(output => 
                        return scanDir(dir.path)
                            .then(files => 
                                return output.concat(files);
                            );
                    );
                , Promise.resolve(files));
        );

Async / await 绝对是最容易阅读的解决方案:

async function scanDir(path) 

    const output = [];
    const files = await client.scanRemoteDirectory(path);
    for (const file of files) 
        if (file.type !== 'directory') 
            output.push(file);
            continue;
        

        const subFiles = await scanDir(file.path);
        output = output.concat(subFiles);       
    

    return output;

【讨论】:

【参考方案2】:

我会让 then 处理程序异步,以便您可以在循环中使用 await

 const scanDir(path) 
  // Scan the remote directory for files and sub-directories
  return client.scanRemoteDirectory(path)
    .then(async files => 
       for (const file of files) 
          // If a sub-directory is found, scan it too
          if (file.type === 'directory') 
            await scanDir(file.path) // Recursive call
          
      
   )
 

【讨论】:

不应该也返回内部的scanDir吗?否则它将执行,但响应将不正确。不过,这是一种有趣的方法,没有考虑 async / await。 如果您仍然使用await,为什么所有这些then 并发症?就做files = await client.scanRemote(); for file of files etc

以上是关于基于递归 Promise 的目录读取的主要内容,如果未能解决你的问题,请参考以下文章

Promise 用法

将递归异步函数转换为 Promise

js进阶五(js回调promisepromise嵌套异常处理jquery使用promise)

如何使用 Promise Kit 调用递归函数?

递归执行promise nodejs

使用Promise表行走/递归