async/await 你可能正在将异步写成同步

前言

你是否察觉到自己随手写的异步函数,实际却是"同步"的效果!

正文

以一个需求为例:获取给定目录下的全部文件,返回所有文件的路径数组。

第一版

思路很简单:读取目录内容,如果是文件,添加进结果数组,如果还是目录,我们递归执行。

js 复制代码
import path from 'node:path'
import fs from 'node:fs/promises'
import { existsSync } from 'node:fs'

async function findFiles(root) {
    if (!existsSync(root)) return
    
    const rootStat = await fs.stat(root)
    if (rootStat.isFile()) return [root]

    const result = []
    const find = async (dir) => {
        const files = await fs.readdir(dir)
        for (let file of files) {
            file = path.resolve(dir, file)
            const stat = await fs.stat(file)
            if (stat.isFile()) {
                result.push(file)
            } else if (stat.isDirectory()) {
                await find(file)
            }
        }
    }
    await find(root)
    return result
}

机智的你是否已经发现了问题?

我们递归查询子目录的过程是不需要等待上一个结果的,但是第 19 行代码,只有查询完一个子目录之后才会查询下一个,显然让并发的异步,变成了顺序的"同步"执行。

那我们去掉 19 行的 await 是不是就可以了,当然不行,这样的话 await find() 在没有完全遍历目录之前就会立刻返回,我们无法拿到正确的结果。

思考一下,怎么修改它呢?......让我们看第二版代码。

第二版

js 复制代码
import path from 'node:path'
import fs from 'node:fs/promises'
import { existsSync } from 'node:fs'

async function findFiles(root) {
    if (!existsSync(root)) return
    
    const rootStat = await fs.stat(root)
    if (rootStat.isFile()) return [root]

    const result = []
    const find = async (dir) => {
        const task = (await fs.readdir(dir)).map(async (file) => {
            file = path.resolve(dir, file)
            const stat = await fs.stat(file)
            if (stat.isFile()) {
                result.push(file)
            } else if (stat.isDirectory()) {
                await find(file)
            }
        })
        return Promise.all(task)
    }
    await find(root)
    return result
}

我们把每个子目录内容的查询作为独立的任务,扔给 Promise.all 执行,就是这个简单的改动,性能得到了质的提升,让我们看看测试,究竟能差多少。

对比测试

js 复制代码
console.time('v1')
const files1 = await findFiles1('D:\\Videos')
console.timeEnd('v1')

console.time('v2')
const files2 = await findFiles2('D:\\Videos')
console.timeEnd('v2')

console.log(files1?.length, files2?.length)

版本二快了三倍不止,如果是并发的接口请求被不小心搞成了顺序执行,差距比这还要夸张。

相关推荐
kyriewen1 小时前
面试官让我手写虚拟列表——AI生成的版本,快速滚动几下就白屏了
前端·javascript·面试
IT_陈寒1 小时前
Vite热更新失效?你可能漏了这个配置
前端·人工智能·后端
Revolution612 小时前
React 组件重新渲染时,到底重新执行了什么
前端·react.js·面试
用户2181697049303 小时前
Flutter(四)Dart语法 空安全 运算符 流程控制
前端
许彰午3 小时前
政务督办的分合模式:主办协办的并发审批
前端·javascript·政务
用户69371750013843 小时前
AI时代,程序员该往哪走?
前端·后端
陳陈陳3 小时前
🚀 前端流式输出革命:SSE + BFF 架构从零到一实战指南
vue.js·架构·node.js
ttwuai3 小时前
GoFrame 后台日志清空失败:无 WHERE 删除为什么被拦住
前端·golang
易筋紫容3 小时前
创建型模式:对象的诞生艺术
开发语言·前端·javascript
Revolution614 小时前
第一次运行 Node.js:终端里的 JavaScript 怎样执行
后端·面试·node.js