es-toolkit 的 `after` 函数:按调用次数控制执行时机的完整指南(compat 兼容版与原生版对比)
2026/9/15 13:26:06 网站建设 项目流程

es-toolkit 的after函数:按调用次数控制执行时机的完整指南(compat 兼容版与原生版对比)

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

after是 es-toolkit 中一个典型的函数式工具:它包装一个函数,使其只有在被调用达到指定次数后才会真正执行。本文以 compat 兼容版文档 为主线,结合es-toolkit/compat与原生es-toolkit两套实现源码与测试用例,完整讲解其用法、边界行为、底层原理,以及何时该用哪个版本,帮助你用它在异步并发、初始化完成回调等场景中写出更清晰的控制流。

一、after是什么:一句话核心语义

after(n, func)创建一个受限函数(restricted function),前n - 1次调用不会触发func,从第n次调用起,func才会被执行,并且之后每次调用都会继续执行。

const restrictedFunction = after(n, func);

它典型的应用场景包括:

  • 多个异步操作完成后执行回调:例如同时发起多个请求,全部完成后才更新 UI;
  • 初始化阶段结束后激活某个功能:在指定次数的事件(如按钮点击、拖拽、资源加载)发生后开启后续逻辑;
  • 限流式的一次性开关逻辑:与beforeonce等兄弟函数配合,构建调用次数驱动的状态机。

二、基本用法:从第 n 次调用开始执行

es-toolkit/compat导入:

import { after } from 'es-toolkit/compat'; // 基础用法 const logAfterThree = after(3, () => { console.log('Executed from the 3rd call!'); }); logAfterThree(); // Not executed logAfterThree(); // Not executed logAfterThree(); // Logs "Executed from the 3rd call!" logAfterThree(); // Logs "Executed from the 3rd call!" (continues to execute)

注意:第 3 次调用之后,函数会持续执行,而不是只执行一次。这与once的"只执行一次"语义不同。

组合多个异步任务完成回调

after最常见的实战用法是等待一批异步操作全部结束后再执行聚合回调:

import { after } from 'es-toolkit/compat'; const tasks = ['task1', 'task2', 'task3']; const allTasksComplete = after(tasks.length, () => { console.log('All tasks completed!'); }); // Called when each task completes tasks.forEach(task => { performAsyncTask(task, () => { console.log(`${task} complete`); allTasksComplete(); // Logs "All tasks completed!" on the 3rd call }); });

这里n直接取tasks.length,无需硬编码任务数量,新增任务时逻辑自动保持正确。

传入 0 或负数:从第一次调用就立即执行

import { after } from 'es-toolkit/compat'; const immediate = after(0, () => console.log('Executed immediately')); immediate(); // "Executed immediately" const negative = after(-1, () => console.log('Executed immediately')); negative(); // "Executed immediately"

这是compat 版特有的行为:当n <= 0时,第一次调用就会执行func。而原生版对负数会直接抛错(详见下文第五节),两者存在明显的行为差异,混用时务必注意。

参数与返回值

  • n(number):触发func执行前所需的调用次数。
  • func(TFunc):被限制执行的函数。
  • 返回值 (TFunc):一个新的受限函数,从第n次调用起执行原函数。

三、源码级剖析:compat 版是如何实现的

compat 版实现位于 src/compat/function/after.ts,完整逻辑如下:

import { toInteger } from '../util/toInteger.ts'; export function after<TFunc extends (...args: any[]) => any>(n: number, func: TFunc): TFunc { if (typeof func !== 'function') { throw new TypeError('Expected a function'); } n = toInteger(n); return function (this: any, ...args: Parameters<TFunc>) { if (--n < 1) { return func.apply(this, args); } } as TFunc; }

可以拆解出三个关键设计:

  1. 入参校验func必须是一个函数,否则抛出TypeError('Expected a function')。测试 src/compat/function/after.spec.ts 中after(1, 42 as any)即验证了该错误路径。
  2. 整数化处理n会先经过toInteger(来自 src/compat/util/toInteger.ts)转换。这意味着NaN会被转为0,小数会被截断取整——这正是文档中"0 或负数立即执行"行为背后的机制。测试中testAfter(NaN, 1)结果为1,证实NaN被强制转为0后第一次调用即执行。
  3. 闭包计数器与 this 绑定:返回的受限函数是普通函数(非箭头函数),通过--n < 1判断是否放行,并使用func.apply(this, args)保留调用方的this上下文。测试中object.after()能正确累加this.count(得到2),验证了 this 绑定行为。

compat 版为什么"慢"?

兼容版文档明确指出:由于涉及复杂的类型校验和整数转换处理typeof func检查、toInteger转换),该版本运行速度较慢。因此文档给出的建议是:

优先使用 es-toolkit 原生、更快的 after。

这也解释了为什么 es-toolkit 提供了两个after——一个追求与 lodash 完全兼容的语义(compat),一个追求极致性能与更严格的类型约束(原生)。

四、原生版实现:更快、更严格

原生版位于 src/function/after.ts:

export function after<F extends (...args: any[]) => any>( n: number, func: F ): (...args: Parameters<F>) => ReturnType<F> | undefined { if (!Number.isInteger(n) || n < 0) { throw new Error(`n must be a non-negative integer.`); } let counter = 0; return (...args: Parameters<F>) => { if (++counter >= n) { return func(...args); } return undefined; }; }

与 compat 版相比,原生版有三个显著差异:

对比维度原生版es-toolkit/function兼容版es-toolkit/compat
导入路径es-toolkit/functiones-toolkit/compat
非法n的处理非整数或负数直接throw new ErrortoInteger自动转换,NaN0,不抛错
非法func的处理无显式校验(由调用时自然失败)立即抛TypeError('Expected a function')
this绑定返回箭头函数,不保留调用方this返回普通函数,通过apply保留this
计数器实现独立counter变量递增判断直接对n递减判断
返回值类型(...args) => ReturnType \| undefined断言为TFunc

原生版文档(docs/reference/function/after.md)与对应测试(src/function/after.spec.ts)进一步确认了这些边界:

  • 负数与NaN均会抛出[Error: n must be a non-negative integer.]
  • after(0)首次调用即执行;
  • n次调用前返回值均为undefined,参数会原样透传给func

选型建议:如果你在迁移 lodash 代码、依赖 lodash 的宽松语义(如NaN自动归零、负数立即执行、保留this),请使用 compat 版;如果你编写新代码、追求更高执行效率与严格的参数约束,请使用原生版。

五、与before的关系:一对相反的兄弟

compat 版源码注释将其描述为 "The opposite of_.before"。before限制函数最多执行 n - 1 次(第 n 次起不再执行),而after第 n 次起才开始执行。二者组合可以精确表达"执行窗口":例如用before(5, cb)限制事件处理最多触发 4 次,或用after(3, cb)保证第 3 次之后才开始响应。它们的实现思路也互为镜像——before内部同样是--n > 0形式的计数器判断。

六、测试佐证:行为契约一目了然

两套测试用例共同构成了after的行为契约:

  • 计数语义(src/compat/function/after.spec.ts):testAfter(5, 4)为 0(未到第 5 次不执行)、testAfter(5, 5)为 1(第 5 次执行一次)、testAfter(0, 0)为 0(不立即执行)、testAfter(0, 1)为 1(调用一次即执行);
  • 严格校验(src/function/after.spec.ts):负数、NaN抛错;n次调用前返回undefined;参数正确透传(afterFn(1, 2)返回3)。

这些测试文件可以直接作为行为参考,也可以在你的项目中以相同用例验证自定义的计数逻辑。

七、小结

after用一次闭包计数器,把"次数条件"从业务代码中抽离出来,让异步并发完成回调、初始化完成触发等场景的表达更加声明化。理解 compat 版(宽松、兼容 lodash、保留this)与原生版(严格、快速、无类型转换开销)的差异,你就能在迁移旧代码与编写新代码之间做出正确选择。若追求极致性能,请按官方建议优先使用原生版 after(es-toolkit/function),并将 compat 版保留给确实需要 lodash 语义的场景。

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询