【问题标题】:Is this valid syntax for node/promisify async function?这是 node/promisify 异步函数的有效语法吗?
【发布时间】:2021-07-26 01:59:35
【问题描述】:

我不小心输入了await(await stat(content...,它成功了。不确定这是否是有效的语法,或者有更好的方法吗?我正在尝试读取所有目录文件并且与我的正则表达式不匹配。

const fs = require('fs')
const path = require('path')
const content = path.resolve('.') + '/docs' + '/'
const util = require('util');
const stat = util.promisify(fs.stat)
const readDir = util.promisify(fs.readdir)
const directories = 'docs/';
const exclude = new RegExp(/^(adir|\.somedir)/,'i');
let newFiles = {}

async function main(){
    const ls = await readDir(directories)
    console.log('starting....');
    let newArray = []
     for (let index = 0; index < ls.length; index++) {
               let x =  await (await stat(content + ls[index])).isDirectory()
               let file = ls[index]
               if (x && !(exclude.test(file))){newArray.push(file)}
               console.log('x is ',x);
        }   
    console.log('new filtered array: ', newArray);
}

【问题讨论】:

  • 如果语法无效,它将无法工作。你可以等待非承诺;你只是得到了价值。
  • 这是脚本还是某些服务器代码的一部分?如果是前者,那么使用 ...Sync 函数会更容易而忘记承诺。
  • @georg 不,不是,谢谢。我认为CertainPerformance 想出了一个更好的方法。
  • 在异步情况下,您不需要 stat 调用。见nodejs.org/api/fs.html#fs_fspromises_readdir_path_options,选项withFileTypes
  • @georg 正在调查 const files = await readdir(directories,{withFileTypes:true});

标签: javascript node.js promise node-promisify


【解决方案1】:

很多功劳归功于 gorg 和某些性能。这是一个简单的解决方案。

const { stat, readdir } = require('fs').promises;

async function main() {
    try {
        const getFiles = await readdir(directories, { withFileTypes: true })
        let foo = getFiles.filter(x=> x.isDirectory() && ! excludeDir.test(x.name))
        .map(f=>f.name);
    } catch (err) {
    console.error(err);
    }
}

【讨论】:

    【解决方案2】:

    ls

    我的建议是不要把所有的鸡蛋都放在一个篮子里。我们可以使用 Node 的 fs.Dirent 对象编写一个超快的 ls 函数,并绕过对 each 文件的慢速 fs.stat 调用的需要 -

    // fsext.js
    
    import { readdir } from "fs/promises"
    import { join } from "path"
    
    async function* ls (path = ".")
    { yield { dir: path }
      for (const dirent of await readdir(path, { withFileTypes: true }))
        if (dirent.isDirectory())
          yield *ls(join(path, dirent.name))
        else
          yield { file: join(path, dirent.name) }
    }
    
    async function toArray (iter)
    { const r = []
      for await (const v of iter)
        r.push(v)
      return r
    }
    
    export { ls, toArray }
    
    // main.js
    
    import { ls, toArray } from "./fsext.js"
    
    toArray(ls("./node_modules")).then(console.log, console.error)
    

    为了测试它,让我们添加一些流行的npm 包,这样我们就有一个很大的层次结构来测试我们的程序。我们将安装批次并计算目录和文件的数量 -

    $ npm install async chalk commander debug express immutable lodash moment prop-types react react-dom request webpack
    
    $ find ./node_modules | wc -l
    
    5453
    

    现在让我们运行我们的程序并time它-

    $ time node main.js
    
    [
      { dir: './node_modules' },
      { dir: 'node_modules/.bin' },
      { file: 'node_modules/.bin/acorn' },
      { file: 'node_modules/.bin/browserslist' },
      { file: 'node_modules/.bin/loose-envify' },
      { file: 'node_modules/.bin/mime' },
      { file: 'node_modules/.bin/sshpk-conv' },
      { file: 'node_modules/.bin/sshpk-sign' },
      { file: 'node_modules/.bin/sshpk-verify' },
      { file: 'node_modules/.bin/terser' },
      { file: 'node_modules/.bin/uuid' },
      { file: 'node_modules/.bin/webpack' },
      { file: 'node_modules/.package-lock.json' },
      { dir: 'node_modules/@types' },
      { dir: 'node_modules/@types/eslint' },
      { file: 'node_modules/@types/eslint/LICENSE' },
      { file: 'node_modules/@types/eslint/README.md' },
      { file: 'node_modules/@types/eslint/helpers.d.ts' },
      { file: 'node_modules/@types/eslint/index.d.ts' },
      { dir: 'node_modules/@types/eslint/lib' },
       ... 5433 more items
    ]
    
    node main.js  0.09s user 0.02s system 116% cpu 0.099 total
    

    目录

    如果我们只想要目录,我们可以将 dirs 写成我们的通用 ls 的简单特化 -

    // fsext.js (continued)
    
    async function* dirs (path)
    { for await (const f of ls(path))
        if (f.dir)
          yield f.dir
    }
    
    $ find ./node_modules -type d | wc -l
    
    457
    

    现在将它与我们的程序进行比较

    // main.js
    
    import { dirs, toArray } from "./fsext.js"
    
    toArray(dirs("./node_modules")).then(console.log, console.error)
    
    $ time node.main.js
    
    [
      './node_modules',
      'node_modules/.bin',
      'node_modules/@types',
      'node_modules/@types/eslint',
      'node_modules/@types/eslint/lib',
      'node_modules/@types/eslint/lib/rules',
      'node_modules/@types/eslint/rules',
      'node_modules/@types/eslint-scope',
      'node_modules/@types/estree',
      'node_modules/@types/json-schema',
      'node_modules/@types/node',
      'node_modules/@types/node/assert',
      'node_modules/@types/node/dns',
      'node_modules/@types/node/fs',
      'node_modules/@types/node/stream',
      'node_modules/@types/node/timers',
      'node_modules/@types/node/ts3.6',
      'node_modules/@webassemblyjs',
      'node_modules/@webassemblyjs/ast',
      'node_modules/@webassemblyjs/ast/esm',
      ... 437 more items
    ]
    
    node main2.js  0.09s user 0.02s system 108% cpu 0.099 total
    

    排除

    如果我们想exclude某些目录或文件,我们也可以通用写-

    // fsext.js (continued)
    
    async function* exclude (iter, test)
    { for await (const v of iter)
        if (Boolean(test(v)))
          continue
        else
          yield v
    }
    
    // main.js
    
    import { dirs, exclude, toArray } from "./fsext.js"
    
    toArray(exclude(dirs("./node_modules"), v => /@/.test(v)))
      .then(console.log, console.error)
    
    
    $ time node main.js
    
    [
      './node_modules',
      'node_modules/.bin',
      'node_modules/accepts',
      'node_modules/acorn',
      'node_modules/acorn/bin',
      'node_modules/acorn/dist',
      'node_modules/ajv',
      'node_modules/ajv/dist',
      'node_modules/ajv/lib',
      'node_modules/ajv/lib/compile',
      'node_modules/ajv/lib/dot',
      'node_modules/ajv/lib/dotjs',
      'node_modules/ajv/lib/refs',
      'node_modules/ajv/scripts',
      'node_modules/ajv-keywords',
      'node_modules/ajv-keywords/keywords',
      'node_modules/ajv-keywords/keywords/dot',
      'node_modules/ajv-keywords/keywords/dotjs',
      'node_modules/ansi-styles',
      'node_modules/array-flatten',
      ... 351 more items
    ]
    
    node main.js  0.09s user 0.02s system 105% cpu 0.104 total
    

    重组

    在我们的文件系统扩展模块fsext 中,我们编写了两个可用于any 迭代的函数,而不仅仅是lsdirs。我建议将它们分解成它们自己的iter 模块。这种类型的重组有助于在整个程序中解耦关注点并最大限度地重用代码 -

    // iter.js
    
    async function* empty () {}
    
    async function* exclude (iter = empty(), test = Boolean)
    { for await (const v of iter)
        if (Boolean(test(v)))
          continue
        else
          yield v
    }
    
    async function toArray (iter = empty())
    { const r = []
      for await (const v of iter)
        r.push(v)
      return r
    }
    
    export { empty, exclude, toArray }
    
    // fsext.js
    
    import { readdir } from "fs/promises"
    import { join } from "path"
    
    async function* ls (path = ".")
    { yield { dir: path }
      for (const dirent of await readdir(path, { withFileTypes: true }))
        if (dirent.isDirectory())
          yield *ls(join(path, dirent.name))
        else
          yield { file: join(path, dirent.name) }
    }
    
    async function* dirs (path)
    { for await (const f of ls(path))
        if (f.dir)
          yield f.dir
    }
    
    async function* files (path)
    { for await (const f of ls(path))
        if (f.file)
          yield f.file
    }
    
    export { ls, dirs, files }
    
    // main.js
    
    import { dirs } from "./fsext.js"
    import { exclude, toArray } from "./iter.js"
    
    const somePath = "..."
    const someTest = v => ...
    
    toArray(exclude(dirs(somePath), someTest))
      .then(console.log, console.error)
    

    【讨论】:

    • 谢谢谢谢,我得去看看。我现在确实发布了一个答案,但似乎没有你的那么复杂。我喜欢产量。
    • @ritchie 我很乐意提供帮助。如果您还有其他问题,请不要害羞:D
    • 是否还有一个名为 join() 的函数?我正在尝试让您的脚本正常工作。必须将所有 Imports 更改为 const { stat, readdir ,join} = require('fs').promises; 我知道有 path.join() 。哦,我用 7000 个文件测试了我的文件,只需几分之一秒。我可能永远不会在一个目录中有 7000 个文件,哈哈。我正在测试你的。
    • 好的,我现在开始工作了,你想出这个真是太疯狂了。它并不比我用 7000 个文件测试的快,但是你又是递归的,而我的不是。我不需要网站的递归。如果你想知道你的是:079s,084,019s... 而我的是 065s,040s,018s。
    • join 来自path 模块。我不记得哪个版本的节点添加了对import 的支持,但它已经存在了一段时间。要启用它,您必须将{ "type": "module" } 添加到package.json。 7,000 个文件可能看起来很多,但这只是为了证明ls 即使在大型、深度文件系统上运行也很快。我选择递归过程是因为文件层次结构在结构上是递归的,并且更容易推断出与其操作的数据形状相匹配的程序。一切顺利:D
    【解决方案3】:

    由于它有效,语法是有效的 - 但代码令人困惑,可能不应该在没有一些调整的情况下使用。这里要知道的重要一点是它对await 有效,这不是Promise。如果右边的表达式是 Promise,则整个事情将解析为已解析的 Promise 的值;如果右边的表达式不是 Promise,则整个表达式将解析为该值。那就是:

    await Promise.resolve(5)
    

    本质上是一样的

    await 5
    

    但是虽然第二个有效,但它令人困惑 - 最好只 await 是 Promise 的东西。 fs.isDirectory 不返回 Promise,因此最好从中删除 await

    对于您正在做的事情,还有一个更好的方法:改用Promise.all,这样就可以一次搜索目录中的所有项目,而不必一个接一个地等待它们。如果目录中有很多项目,您当前的代码将花费很长时间 - 这不是必需的。

    您还可以通过使用正则表达式而不是 new RegExp 来简化正则表达式。

    const exclude = /^(?:adir|\.somedir)/i;
    
    async function main() {
        const filenames = await readDir(directories);
        const newArray = await Promise.all(
            filenames.map(async (filename) => {
                const fileStat = await stat(content + filename);
                if (fileStat.isDirectory && !(exclude.test(file))) {
                    return filename;
                }
            })
        )
        const results = newArray.filter(Boolean);
        console.log('new filtered array: ', results);
    }
    

    您也可以考虑改用fs.promises,而不是使用util.promisify

    const { stat, readdir } = require('fs').promises;
    

    【讨论】:

    • promisify 确实返回了一个承诺。
    • @ritchie isDirectory 没有返回 Promise,所以 awaiting 它没有意义。仅使用 一个 await,作为stat
    • 我的意思是 stat 。
    • 我得到 readDir 不是一个函数,试图解决这个问题。甚至改在const { stat, readdir } = require('fs').promises;
    • @ritchie 确保使用一致的大小写。函数名是readdir,不是readDir
    【解决方案4】:

    isDirectory 返回一个boolean,而不是Promise&lt;boolean&gt;,所以第二个await 是多余的,你可以写(await stat(content + ls[index])).isDirectory()

    【讨论】:

    • 是的,你是对的。有趣的是,这个 for 循环是如何工作的,但许多人很难使用异步循环,甚至创建博客试图弄清楚如何在异步中使用循环吧?
    • await 可以在 for 循环和任何代码中工作,它只是在底层使用 Promise 链接。您应该考虑@CertainPerformance 答案,因为您的输出数组仅取决于每个输入文件路径的相应文件,因此您可以并行化任务并大幅提高性能
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-13
    • 2019-10-09
    • 1970-01-01
    • 2020-09-11
    • 2017-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多