import type { AnyAsyncFunction, AnyFunction, FunctionComposeAll, FunctionPipeAll, } from "#Source/type/index.ts" import { asIs } from "./helper.ts" import { isAsyncFunction, isSyncFunction } from "./is.ts" /** * @description Immediately invoke a function with the provided arguments. * * @example * ``` * // Expect: 3 * const example1 = functionIife((a: number, b: number) => a + b, 1, 2) * // Expect: "ok" * const example2 = functionIife(() => "ok") * ``` */ export const functionIife = ( fn: T, ...args: Parameters ): ReturnType => { // oxlint-disable-next-line no-unsafe-return return fn(...args) } /** * @description 表示只会保留首次执行结果的一次性函数。 */ export type FunctionOnce = (...args: Parameters) => ReturnType /** * @description 生成一个只在首次调用时真正执行的函数。 * * @example * ``` * const exampleFn1 = functionOnce((x: number) => x * 2) * // Expect: 10 * const example1 = exampleFn1(5) * // Expect: 10 * const example2 = exampleFn1(10) * * let callCount = 0 * const exampleFn2 = functionOnce((x: number) => x * 2, (times) => { callCount = times }) * exampleFn2(5) * exampleFn2(10) * // Expect: 2 * const example3 = callCount * ``` */ export const functionOnce = ( fn: T, timesSubscriber?: (times: number) => void, ): FunctionOnce => { let called = false let result: ReturnType let times = 0 return (...args) => { times = times + 1 if (called === false) { result = fn(...args) called = true } if (times >= 2 && timesSubscriber !== undefined) { timesSubscriber(times) } // oxlint-disable-next-line no-unsafe-return return result } } /** * @description 表示简单防抖后的函数类型。 */ export type FunctionDebouncedSimple = (...args: Parameters) => void /** * @description 生成一个简单防抖函数,在最后一次调用后的指定时间再触发原函数。 * * @example * ``` * const records: number[] = [] * const exampleFn1 = functionDebounceSimple((x: number) => { records.push(x) }, 10) * exampleFn1(5) * exampleFn1(10) * // Expect: records.length === 0 * const example1 = records.length === 0 * ``` */ export const functionDebounceSimple = ( fn: T, ms: number, ): FunctionDebouncedSimple => { let timer: ReturnType return (...args) => { clearTimeout(timer) timer = setTimeout(() => { fn(...args) }, ms) } } /** * @description 表示返回 `Promise` 的防抖函数类型。 */ export type FunctionDebounced = ( ...args: Parameters ) => Promise> /** * @description 生成一个返回 `Promise` 的防抖函数,并在防抖窗口内合并并发调用。 * * @example * ``` * const exampleFn1 = debounce(async (x: number) => x * 2, 10) * const example1 = exampleFn1(5) * const example2 = exampleFn1(10) * // Expect: example1 instanceof Promise * const example3 = example1 instanceof Promise * // Expect: example2 instanceof Promise * const example4 = example2 instanceof Promise * ``` */ export const debounce = (fn: T, ms: number): FunctionDebounced => { let timer: ReturnType let waiting: AnyFunction[] = [] return async (...args) => { clearTimeout(timer) timer = setTimeout( (async () => { const res = await fn(...args) waiting.forEach((resolve) => { resolve(res) }) waiting = [] }) as () => void, ms, ) // oxlint-disable-next-line no-unsafe-return return await new Promise((resolve) => { waiting.push(resolve) }) } } const THROTTLE_TYPE_ERROR = new TypeError("Throttle: fn must be a SyncFunction or AsyncFunction") /** * @description 表示基于时间窗口的简单节流函数类型。 */ export type FunctionThrottleTimeSimple = (...args: Parameters) => void /** * @description 生成一个简单时间节流函数,在指定时间窗口内最多执行一次原函数。 * * 当 `strict` 为 `true` 时,节流窗口会同时考虑等待时长与目标函数执行时长。 * * @example * ``` * let called = 0 * const exampleFn1 = functionThrottleTimeSimple(() => { called += 1 }, 100) * exampleFn1() * exampleFn1() * // Expect: 1 * const example1 = called * ``` */ export const functionThrottleTimeSimple = ( fn: T, ms: number, strict = false, ): FunctionThrottleTimeSimple => { let isCalling = false // NOTE: 对于不同的目标函数,分别返回不同的结果,避免在每次运行时再做条件判断。 if (isSyncFunction(fn) === true) { return (...args) => { if (isCalling === false) { isCalling = true setTimeout(() => { isCalling = false }, ms) fn(...args) } } } else if (isAsyncFunction(fn) === true) { return (...args) => { let isExecuted = false let timerExpired = false if (isCalling === false) { isCalling = true setTimeout(() => { if (strict === false) { isCalling = false } else { if (isExecuted === true) { isCalling = false } else { timerExpired = true } } }, ms) void (fn as AnyAsyncFunction)(...args).finally(() => { isExecuted = true if (timerExpired === true) { isCalling = false } }) } } } else { throw THROTTLE_TYPE_ERROR } } /** * @description 表示执行期间丢弃重复调用的简单节流函数类型。 */ export type FunctionThrottleSimple = (...args: Parameters) => void /** * @description 生成一个在执行期间忽略重复调用的节流函数。 * * @example * ``` * let called = 0 * const exampleFn1 = functionThrottleSimple(() => { called += 1 }) * exampleFn1() * exampleFn1() * // Expect: 1 * const example1 = called * ``` */ export const functionThrottleSimple = (fn: T): FunctionThrottleSimple => { let isCalling = false if (isSyncFunction(fn) === true) { return (...args) => { if (isCalling === false) { isCalling = true fn(...args) isCalling = false } } } else if (isAsyncFunction(fn) === true) { return (...args) => { if (isCalling === false) { isCalling = true void (fn as AnyAsyncFunction)(...args).then(() => { isCalling = false }) } } } else { throw THROTTLE_TYPE_ERROR } } /** * @description 表示返回 `Promise` 的时间节流函数类型。 */ export type FunctionThrottleTime = ( ...args: Parameters ) => Promise> /** * @description 生成一个返回 `Promise` 的时间节流函数。 * * 当 `strict` 为 `true` 时,节流窗口会同时考虑等待时长与目标函数执行时长。 * * @example * ``` * const exampleFn1 = throttleTime(async (value: number) => value * 2, 100) * const example1 = exampleFn1(2) * const example2 = exampleFn1(3) * // Expect: example1 instanceof Promise * const example3 = example1 instanceof Promise * // Expect: example2 instanceof Promise * const example4 = example2 instanceof Promise * ``` */ export const throttleTime = ( fn: T, ms: number, strict = false, ): FunctionThrottleTime => { let isCalling = false let waiting: AnyFunction[] = [] return async (...args) => { let isExecuted = false let timerExpired = false if (isCalling === false) { isCalling = true setTimeout(() => { if (strict === false) { isCalling = false } else { if (isExecuted === true) { isCalling = false } else { timerExpired = true } } }, ms) // NOTE: 一般来说,如果目标函数是同步函数,节流版本也会同步执行, // 如果是异步函数,节流版本会异步运行, // 需要拿到结果的节流版本一律按照异步处理。 void Promise.resolve(fn(...args)).then((res) => { // 先广播执行结果 waiting.forEach((resolve) => { resolve(res) }) waiting = [] // 后重置节流状态 isExecuted = true if (timerExpired === true) { isCalling = false } }) } // oxlint-disable-next-line no-unsafe-return return (await new Promise((resolve) => { waiting.push(resolve) })) as ReturnType } } /** * @description 表示合并并发调用的节流函数类型。 */ export type FunctionThrottle = ( ...args: Parameters ) => Promise> /** * @description 生成一个返回 `Promise` 的节流函数,并合并并发调用。 * * @example * ``` * const exampleFn1 = functionThrottle(async () => "ok") * const example1 = exampleFn1() * const example2 = exampleFn1() * // Expect: example1 instanceof Promise * const example3 = example1 instanceof Promise * // Expect: example2 instanceof Promise * const example4 = example2 instanceof Promise * ``` */ export const functionThrottle = (fn: T): FunctionThrottle => { let isCalling = false let waiting: AnyFunction[] = [] return async () => { if (isCalling === false) { isCalling = true void Promise.resolve(fn()).then((res) => { // 先广播执行结果 waiting.forEach((resolve) => { resolve(res) }) waiting = [] // 后重置节流状态 isCalling = false }) } // oxlint-disable-next-line no-unsafe-return return (await new Promise((resolve) => { waiting.push(resolve) })) as ReturnType } } /** * @description Compose functions from right to left. * * @example * ``` * const addOne = (value: number) => value + 1 * const double = (value: number) => value * 2 * const composed = functionCompose(addOne, double) * // Expect: 5 * const example1 = composed(2) * ``` */ export const functionCompose = ( ...fns: Fns ): FunctionComposeAll => { const reversedFns = fns.toReversed() const initialFunction = reversedFns.shift() ?? asIs const composedFunction = reversedFns.reduce((g, f) => { // oxlint-disable-next-line no-explicit-any explicit-function-return-type return (...args: any[]) => { // oxlint-disable-next-line no-unsafe-return no-unsafe-argument return f(g(...args)) } }, initialFunction) return composedFunction as FunctionComposeAll } /** * @description Pipe functions from left to right. * * @example * ``` * const addOne = (value: number) => value + 1 * const double = (value: number) => value * 2 * const piped = functionPipe(addOne, double) * // Expect: 6 * const example1 = piped(2) * ``` */ export const functionPipe = (...fns: Fns): FunctionPipeAll => { return functionCompose(...fns.toReversed()) as FunctionPipeAll } /** * @description 表示带参数缓存能力的记忆化函数类型。 */ export type FunctionMemorized = (...args: Parameters) => ReturnType // oxlint-disable-next-line no-explicit-any const defaultMemorizeHasher = (...args: any[]): string => JSON.stringify(args) /** * @description Generate a memoized function that caches results by arguments. * * @example * ``` * let calls = 0 * const sum = (a: number, b: number) => { * calls += 1 * return a + b * } * const memoized = functionMemorize(sum) * // Expect: 3 * const example1 = memoized(1, 2) * // Expect: 3 * const example2 = memoized(1, 2) * // Expect: calls === 1 * const example3 = calls * ``` */ export const functionMemorize = ( fn: T, hashFunction?: (...args: Parameters) => unknown | undefined, ): FunctionMemorized => { const cache = new Map() const hasher = hashFunction ?? defaultMemorizeHasher return (...args) => { const key = hasher(...args) if (cache.has(key)) { // oxlint-disable-next-line no-unsafe-return return cache.get(key) } const result = fn(...args) cache.set(key, result) // oxlint-disable-next-line no-unsafe-return return result } }