ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于依赖关系的异步并行化:Plate 性能规范中用 better-all 消除部分依赖 Waterfall

基于依赖关系的异步并行化:Plate 性能规范中用 better-all 消除部分依赖 Waterfall 基于依赖关系的异步并行化Plate 性能规范中用 better-all 消除部分依赖 Waterfall【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate本篇围绕 Plate 仓库内置的 React 性能最佳实践规则async-dependenciesDependency-Based Parallelization展开当一个页面或接口中的多个异步请求只有部分依赖关系时如何避免无谓等待把每个任务都尽早启动。读完后你将掌握三种等价的并行化写法——better-all、原生 Promise 链式组合加Promise.all()以及完全独立场景下的标准Promise.all()用法并能判断它们各自的适用边界。规则定位为什么消除 Waterfall被列为最高优先级该规则位于仓库中随 AI 技能一起分发的性能规则集 async-dependencies.md其 frontmatter 标注为impact: CRITICALimpactDescription: 2-10× improvement标签为async, parallelization, dependencies, better-all。之所以给到最高优先级从该规则集的章节定义 _sections.md 可以看到Eliminating Waterfallsasync-前缀被明确写为Waterfalls are the #1 performance killer. Each sequential await adds full network latency.也就是每一次串行await都会把一整段网络/IO 延迟累加到关键路径上。总览文件 SKILL.md 中该规则与async-parallel、async-defer-await、async-api-routes等一起构成第一优先级类别用于写代码、Code Review、重构时自动触发的判定清单。async-dependencies解决的是比完全独立的请求该并行更细的一档问题请求之间只有部分依赖时如何让没有依赖的任务不被拖住。问题场景Promise.all 挡不住部分依赖的串行段规则文档给出的典型反例是先并行取user和config但profile依赖user.id于是被排在第二个串行段const [user, config] await Promise.all([ fetchUser(), fetchConfig() ]) const profile await fetchProfile(user.id)这里的执行时间线是总耗时 ≈max(fetchUser, fetchConfig) fetchProfile。问题不在fetchConfig本身——它其实可以比profile更早结束——而在于结构上profile被写在了第一段await之后即使config与profile互不相关整体仍被迫呈现两波串行的形状。凡是拿到 A 才能查 B但 C 跟 A/B 都无关的页面数据聚合、API 路由、Server Action都会落入这个模式。方案一better-all让每个任务在最早时刻启动规则推荐的写法是引入better-allVercel 的 shuding 维护的工具库把任务声明为依赖图而不是执行顺序import { all } from better-all const { user, config, profile } await all({ async user() { return fetchUser() }, async config() { return fetchConfig() }, async profile() { return fetchProfile((await this.$.user).id) } })其工作机制从代码结构看可以这样理解all()收到一个函数对象后立即按声明启动所有顶层任务——user、config、profile三个请求在调用点同时发出而不是按书写顺序排队profile内部通过this.$.user拿到对user任务结果的 Promise 引用await它时只阻塞profile这一个任务config完全不受影响最终返回值是一个与普通对象形状一致的解构对象{ user, config, profile }调用方心智负担和普通await几乎相同。效果上总耗时从两波串行收敛为max(fetchUser fetchProfile, fetchConfig)的依赖图最长路径——即每个任务都在其前置依赖满足的最早可能时刻开始。对规则 frontmatter 声称的 2~10× 量级改善其来源正是把网络延迟叠加变为网络延迟取最大值。使用better-all的取舍它是一个额外的运行时依赖需要在package.json中声明安装本仓库apps/www/package.json当前并未引入该包此规则属于随技能分发的最佳实践落地到你的应用时需自行添加依赖。它的错误传播、超时等高级语义建议以 better-all 仓库官方文档为准。方案二零依赖等价写法Promise 链 Promise.all规则文档同时给出了不引入任何外部库的等价方案——先把所有 Promise含依赖链构造出来最后再统一Promise.all()const userPromise fetchUser() const profilePromise userPromise.then(user fetchProfile(user.id)) const [user, config, profile] await Promise.all([ userPromise, fetchConfig(), profilePromise ])这段代码与better-all版本在调度上完全等价关键只有一条纪律调用fetchUser()/fetchConfig()的语句必须出现在任何await之前。Promise 是立即开始执行的await只是暂停当前函数。只要先把三个请求都发出去后面的Promise.all就只是等待不再制造新的串行段userPromise在模块执行到该行时即发出fetchUser请求profilePromise通过.then挂到userPromise之后一旦user返回立即发出fetchProfile请求无需等待config三条链并行推进Promise.all只等待最慢的一条。这种先建链、后 await的写法也是规则集中其他条目的通用手法。例如 async-api-routes.mdPrevent Waterfall Chains in API Routes要求在 API 路由 / Server Action 中独立操作立即启动、延后 awaitexport async function GET(request: Request) { const sessionPromise auth() const configPromise fetchConfig() const session await sessionPromise const [config, data] await Promise.all([ configPromise, fetchData(session.user.id) ]) return Response.json({ data, config }) }session、config、data之间同样是部分依赖关系sessionPromise/configPromise提前构造保证了config不会白等auth。该规则结尾也明确指出对于更复杂的依赖链使用better-all自动最大化并行度见 Dependency-Based Parallelization——即本规则与async-dependencies是同一思想在不同复杂度下的两个落点。边界对照完全独立、部分依赖、嵌套三类形态为避免用错工具可以把规则集async-前缀下的几条并行化规则按依赖形状归类依赖形状代表规则推荐写法完全独立async-parallel.md直接Promise.all([fetchA(), fetchB(), fetchC()])一个 round trip 替代三个部分依赖本篇主题async-dependencies.mdbetter-all或先建链后Promise.all逐元素嵌套server-parallel-nested-fetching.md在map内对每个元素链式.then再统一Promise.allRSC 组件树顺序执行server-parallel-fetching.md用组件组合拆分await让 RSC 并行执行各子树其中逐元素嵌套一规则给出了与本篇互补的警告const chatAuthors await Promise.all( chatIds.map(id getChat(id).then(chat getUser(chat.author))) )若写成先Promise.all所有getChat再Promise.all所有getUser100 条中 1 条慢查询会阻塞另外 99 条的嵌套请求把.then链放进每个元素的 Promise 里每个元素独立推进。这与better-all的每个任务在最早时刻启动是同一原则并行化的单位应该下沉到最小依赖单元。仓库实战印证独立聚合用 Promise.allPlate 自身的 Next.js 站点apps/www中可以看到与规则一致的实践。以聚合 LLM 可读 Markdown 的 llm.ts 为例export const getPlateLLMFullMarkdown async (pages: PlateLLMPage[]) { const content await Promise.all( pages.map((page) getPlateLLMPageMarkdownFromPage({ page })) ) return content.join(\n\n) }这里各 page 的 Markdown 化完全相互独立因此直接pages.map(...)并发 一次Promise.all收集属于async-parallel的标准形态若未来某个 page 的转换需要依赖另一个 page 的元数据就可以按本篇的先建链或better-all模式升级为部分依赖并行。落地建议与注意事项先画依赖图再选模式三个请求两两独立 → 普通Promise.all存在用到 A 的结果才能发起 BC 无关的部分依赖 → 本篇两种写法逐条记录的二级请求 → 元素级.then链。零依赖写法优先任务图变复杂再上 better-all手写先建链后 await没有额外依赖但任务一多≥4 个且依赖关系交错手写链容易漏发某个请求或写反依赖方向better-all的声明式任务图更不易出错。不要为看起来更整齐引入串行本规则反例的Promise.all([...]) 后续单独await在语法上完全合法危害只在于多出的串行段重构时应以每个请求最早启动时刻为准绳检查时间线。配套检查并行化之外规则集还建议用 async-defer-await.md把await推迟到真正使用的分支与 async-cheap-condition-before-await.md廉价同步条件先于异步标志判断消除根本不必要的异步调用——先删掉不必要的等待再对剩下的必要等待做并行化收益叠加。适用前提以上模式适用于 Node.js/浏览器中请求发出即开始执行的 Promise 型 IO若底层 API 是惰性执行调用仅返回计划、稍后才真正发起先建链写法不会达到并行效果需要确认底层实现为立即执行语义。小结async-dependencies规则的核心结论可以压缩为一句话串行段不是由await的位置决定的而是由依赖被声明的时机决定的。无论是better-all的声明式任务图还是先创建全部 Promise、最后统一Promise.all()的原生写法目标都一样——把每个请求的启动时刻推进到其前置依赖满足的最早点让总耗时收敛到依赖图最长路径而非所有路径之和。这一模式在页面数据聚合、API 路由、RSC 嵌套查询中都可直接套用也是 Plate 随仓库分发的 React 性能技能中CRITICAL 级优化清单的第一优先级条目。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表