@cloudcome/utils-core 1.23.1 → 1.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/array.cjs CHANGED
@@ -193,6 +193,52 @@ function arrayDiff(refArray, curArray, options) {
193
193
  function arrayRemove(array, indexes) {
194
194
  return array.filter((_item, index) => !indexes.includes(index));
195
195
  }
196
+ /**
197
+ * 从数组中随机取样指定数量的元素。
198
+ *
199
+ * @template T - 数组元素的类型
200
+ * @param array - 要取样的数组。
201
+ * @param options - 取样选项。
202
+ * @returns 包含取样结果的新数组。
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * const arr = [1, 2, 3, 4, 5];
207
+ * arraySample(arr);
208
+ * // => [3](随机取 1 个)
209
+ *
210
+ * arraySample(arr, { count: 3 });
211
+ * // => [5, 1, 3](无序、无放回取 3 个)
212
+ *
213
+ * arraySample(arr, { count: 3, ordered: true });
214
+ * // => [1, 3, 4](保持顺序、无放回)
215
+ *
216
+ * arraySample(arr, { count: 3, replacement: true });
217
+ * // => [4, 2, 4](无序、有放回,可能重复)
218
+ * ```
219
+ */
220
+ function arraySample(array, options) {
221
+ const { count = 1, ordered = false, replacement = false } = options || {};
222
+ let _count = Math.floor(count);
223
+ if (_count < 0) _count = 0;
224
+ const length = array.length;
225
+ if (length === 0 || _count === 0) return [];
226
+ if (replacement) {
227
+ const indices = [];
228
+ for (let i = 0; i < _count; i++) indices.push(Math.floor(Math.random() * length));
229
+ if (ordered) indices.sort((a, b) => a - b);
230
+ return indices.map((i) => array[i]);
231
+ }
232
+ const n = Math.min(_count, length);
233
+ const indices = Array.from({ length }, (_, i) => i);
234
+ for (let i = 0; i < n; i++) {
235
+ const j = i + Math.floor(Math.random() * (length - i));
236
+ [indices[i], indices[j]] = [indices[j], indices[i]];
237
+ }
238
+ const selected = indices.slice(0, n);
239
+ if (ordered) selected.sort((a, b) => a - b);
240
+ return selected.map((i) => array[i]);
241
+ }
196
242
  //#endregion
197
243
  exports.arrayDiff = arrayDiff;
198
244
  exports.arrayEach = arrayEach;
@@ -201,6 +247,7 @@ exports.arrayMove = arrayMove;
201
247
  exports.arrayOmit = arrayOmit;
202
248
  exports.arrayPick = arrayPick;
203
249
  exports.arrayRemove = arrayRemove;
250
+ exports.arraySample = arraySample;
204
251
  exports.isArrayLike = isArrayLike;
205
252
 
206
253
  //# sourceMappingURL=array.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"array.cjs","names":[],"sources":["../src/array.ts"],"sourcesContent":["import { isArray, isObject } from './type';\nimport type { MaybePromise } from './types';\n\n/**\n * 检查给定的值是否为类数组对象。\n *\n * 类数组对象是指具有 `length` 属性且 `length` 属性为非负数的对象。\n *\n * @param unknown - 要检查的值。\n * @returns 如果值是类数组对象,则返回 `true`,否则返回 `false`。\n */\nexport function isArrayLike(unknown: unknown) {\n if (isArray(unknown)) return true;\n\n if (isObject(unknown)) {\n const arrayLike = unknown as { length: unknown };\n return typeof arrayLike.length === 'number' && arrayLike.length >= 0;\n }\n\n return false;\n}\n\n/**\n * 从数组中选择指定索引的元素。\n *\n * @param array - 要从中选择元素的数组。\n * @param indexes - 要选择的元素的索引数组。\n * @returns 包含指定索引元素的新数组。\n */\nexport function arrayPick<T>(array: T[], indexes: number[]) {\n const indexes2 = [...indexes];\n return array.filter((_, i) => {\n const index = indexes2.indexOf(i);\n if (index === -1) return false;\n indexes2.splice(index, 1);\n return true;\n });\n}\n\n/**\n * 从数组中排除指定索引的元素。\n *\n * @param array - 要从中排除元素的数组。\n * @param indexes - 要排除的元素的索引数组。\n * @returns 包含排除指定索引元素后的新数组。\n */\nexport function arrayOmit<T>(array: T[], indexes: number[]) {\n const indexes2 = [...indexes];\n return array.filter((_, i) => {\n const index = indexes2.indexOf(i);\n if (index === -1) return true;\n indexes2.splice(index, 1);\n return false;\n });\n}\n\n/**\n * 遍历数组中的每个元素,并对每个元素执行提供的回调函数。\n *\n * @param array - 要遍历的数组。\n * @param iterator - 对每个元素执行的回调函数。如果回调函数返回 `false`,则提前终止遍历。\n * @param reverse - 是否以相反的顺序遍历数组。默认为 `false`。\n * @returns 无返回值。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3];\n * arrayEach(arr, (item, index) => {\n * console.log(item, index);\n * if (index === 1) return false; // 提前终止遍历\n * });\n * ```\n */\nexport function arrayEach<T>(array: T[], iterator: (item: T, index: number) => false | unknown, reverse = false) {\n const _array = [...array];\n const length = array.length;\n\n if (reverse) {\n for (let i = length - 1; i >= 0; i--) {\n if (iterator(_array[i], i) === false) {\n break;\n }\n }\n } else {\n for (let i = 0; i < length; i++) {\n if (iterator(_array[i], i) === false) {\n break;\n }\n }\n }\n}\n\n/**\n * 异步遍历数组中的每个元素,并对每个元素执行提供的回调函数。\n *\n * @param array - 要遍历的数组。\n * @param iterator - 对每个元素执行的异步回调函数。如果回调函数返回 `false`,则提前终止遍历。\n * @param reverse - 是否以相反的顺序遍历数组。默认为 `false`。\n * @returns 无返回值。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3];\n * await arrayEachAsync(arr, async (item, index) => {\n * await someAsyncOperation(item);\n * if (index === 1) return false; // 提前终止遍历\n * });\n * ```\n */\nexport async function arrayEachAsync<T>(\n array: T[],\n iterator: (item: T, index: number) => MaybePromise<false | unknown>,\n reverse = false,\n) {\n const _array = [...array];\n const length = array.length;\n\n if (reverse) {\n for (let i = length - 1; i >= 0; i--) {\n if ((await iterator(_array[i], i)) === false) {\n break;\n }\n }\n } else {\n for (let i = 0; i < length; i++) {\n if ((await iterator(_array[i], i)) === false) {\n break;\n }\n }\n }\n}\n\n/**\n * 将数组中的元素移动到指定位置。\n *\n * @param array - 要移动元素的数组。\n * @param from - 要移动的元素的起始位置。\n * @param to - 要移动的元素的目标位置。\n * @returns 新的数组,其中包含移动后的元素。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3, 4, 5];\n * const newArr = arrayMove(arr, 1, 3);\n * // 返回 [1, 3, 4, 2, 5]\n * ```\n */\nexport function arrayMove<T>(array: T[], from: number, to: number) {\n const array2 = [...array];\n\n if (from < 0 || from >= array2.length || to < 0 || to >= array2.length) {\n return array2;\n }\n\n const item = array2[from];\n\n array2.splice(from, 1);\n array2.splice(to, 0, item);\n\n return array2;\n}\n\n/**\n * 比较两个数组的差异,返回包含删除、新增和相同元素信息的对象\n *\n * @template T - 数组元素的类型\n * @param {T[]} refArray - 参考数组(原始数组)\n * @param {T[]} curArray - 当前数组(比较数组)\n * @returns {ArrayDiffs<T>} 包含差异信息的对象\n *\n * @example\n * ```typescript\n * const ref = [1, 2, 3];\n * const cur = [2, 3, 4];\n * const diff = arrayDiff(ref, cur);\n * // 返回结果:\n * // {\n * // deletes: [{refIndexes: [0], refValue: 1}],\n * // adds: [{curIndexes: [2], curValue: 4}],\n * // equals: [\n * // {refIndexes: [1], curIndexes: [0], refValue: 2, curValue: 2},\n * // {refIndexes: [2], curIndexes: [1], refValue: 3, curValue: 3}\n * // ]\n * // }\n * ```\n */\nexport type ArrayDiffs<T> = {\n /**\n * 被删除的元素列表\n * @type {Array}\n * @property {number[]} refIndexes - 元素在参考数组中的所有索引位置\n * @property {T} refValue - 被删除的元素值\n */\n deletes: {\n /**\n * 元素在参考数组中的所有索引位置\n * @type {number[]}\n */\n refIndexes: number[];\n /**\n * 被删除的元素值\n * @type {T[]}\n */\n refValues: T[];\n }[];\n\n /**\n * 新增的元素列表\n * @type {Array}\n * @property {number[]} curIndexes - 元素在当前数组中的所有索引位置\n * @property {T} curValue - 新增的元素值\n */\n adds: {\n /**\n * 元素在当前数组中的所有索引位置\n * @type {number[]}\n */\n curIndexes: number[];\n /**\n * 新增的元素值\n * @type {T[]}\n */\n curValues: T[];\n }[];\n\n /**\n * 相同的元素列表\n * @type {Array}\n * @property {number[]} refIndexes - 元素在参考数组中的所有索引位置\n * @property {number[]} curIndexes - 元素在当前数组中的所有索引位置\n * @property {T} refValue - 参考数组中的元素值\n * @property {T} curValue - 当前数组中的元素值\n */\n equals: {\n /**\n * 元素在参考数组中的所有索引位置\n * @type {number[]}\n */\n refIndexes: number[];\n /**\n * 元素在当前数组中的所有索引位置\n * @type {number[]}\n */\n curIndexes: number[];\n /**\n * 参考数组中的元素值\n * @type {T[]}\n */\n refValues: T[];\n /**\n * 当前数组中的元素值\n * @type {T[]}\n */\n curValues: T[];\n }[];\n};\n\nexport type ArrayDiffOptions<T> = {\n getItemKey: (item: T) => unknown;\n};\n\nexport function arrayDiff<T>(refArray: T[], curArray: T[], options?: ArrayDiffOptions<T>): ArrayDiffs<T> {\n const { getItemKey = (item: T) => item } = options || {};\n\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n type Key = any;\n\n const toKeyIndexes = (map: Map<Key, number[]>, item: T) => {\n const key = getItemKey(item);\n return {\n key,\n indexes: map.get(key) || [],\n };\n };\n\n const buildMap = (arr: T[]) => {\n const map = new Map<Key, number[]>();\n\n arr.forEach((item, index) => {\n const { key, indexes } = toKeyIndexes(map, item);\n indexes.push(index);\n map.set(key, indexes);\n });\n\n return map;\n };\n\n const toIndexesArr = (arr: T[], indexes: number[]) => {\n return indexes.map((index) => arr[index]);\n };\n\n const refMap = buildMap(refArray);\n const curMap = buildMap(curArray);\n const deleteSet = new Set<Key>();\n const addSet = new Set<Key>();\n const equalSet = new Set<Key>();\n\n for (const key of refMap.keys()) {\n if (curMap.has(key)) {\n equalSet.add(key);\n } else {\n deleteSet.add(key);\n }\n }\n\n for (const key of curMap.keys()) {\n if (!refMap.has(key)) {\n addSet.add(key);\n }\n }\n\n return {\n deletes: [...deleteSet].map((key) => {\n const indexes = refMap.get(key) || [];\n return {\n refIndexes: indexes,\n refValues: toIndexesArr(refArray, indexes),\n };\n }),\n\n adds: [...addSet].map((key) => {\n const indexes = curMap.get(key) || [];\n return {\n curIndexes: indexes,\n curValues: toIndexesArr(curArray, indexes),\n };\n }),\n\n equals: [...equalSet].map((key) => {\n const refIndexes = refMap.get(key) || [];\n const curIndexes = curMap.get(key) || [];\n return {\n refIndexes,\n curIndexes,\n refValues: toIndexesArr(refArray, refIndexes),\n curValues: toIndexesArr(curArray, curIndexes),\n };\n }),\n };\n}\n\n/**\n * 从数组中移除指定索引的元素\n * @template T - 数组元素的类型\n * @param {T[]} array - 原始数组\n * @param {number[]} indexes - 要移除的元素索引数组\n * @returns {T[]} 移除指定索引元素后的新数组\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3, 4, 5];\n * const newArr = arrayRemove(arr, [1, 3]);\n * // 返回结果: [1, 3, 5]\n * ```\n */\nexport function arrayRemove<T>(array: T[], indexes: number[]) {\n return array.filter((_item, index) => !indexes.includes(index));\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,YAAY,SAAkB;CAC5C,IAAI,aAAA,QAAQ,OAAO,GAAG,OAAO;CAE7B,IAAI,aAAA,SAAS,OAAO,GAAG;EACrB,MAAM,YAAY;EAClB,OAAO,OAAO,UAAU,WAAW,YAAY,UAAU,UAAU;CACrE;CAEA,OAAO;AACT;;;;;;;;AASA,SAAgB,UAAa,OAAY,SAAmB;CAC1D,MAAM,WAAW,CAAC,GAAG,OAAO;CAC5B,OAAO,MAAM,QAAQ,GAAG,MAAM;EAC5B,MAAM,QAAQ,SAAS,QAAQ,CAAC;EAChC,IAAI,UAAU,IAAI,OAAO;EACzB,SAAS,OAAO,OAAO,CAAC;EACxB,OAAO;CACT,CAAC;AACH;;;;;;;;AASA,SAAgB,UAAa,OAAY,SAAmB;CAC1D,MAAM,WAAW,CAAC,GAAG,OAAO;CAC5B,OAAO,MAAM,QAAQ,GAAG,MAAM;EAC5B,MAAM,QAAQ,SAAS,QAAQ,CAAC;EAChC,IAAI,UAAU,IAAI,OAAO;EACzB,SAAS,OAAO,OAAO,CAAC;EACxB,OAAO;CACT,CAAC;AACH;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,UAAa,OAAY,UAAuD,UAAU,OAAO;CAC/G,MAAM,SAAS,CAAC,GAAG,KAAK;CACxB,MAAM,SAAS,MAAM;CAErB,IAAI;OACG,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAC/B,IAAI,SAAS,OAAO,IAAI,CAAC,MAAM,OAC7B;CAAA,OAIJ,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAC1B,IAAI,SAAS,OAAO,IAAI,CAAC,MAAM,OAC7B;AAIR;;;;;;;;;;;;;;;;;;AAmBA,eAAsB,eACpB,OACA,UACA,UAAU,OACV;CACA,MAAM,SAAS,CAAC,GAAG,KAAK;CACxB,MAAM,SAAS,MAAM;CAErB,IAAI;OACG,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAC/B,IAAK,MAAM,SAAS,OAAO,IAAI,CAAC,MAAO,OACrC;CAAA,OAIJ,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAC1B,IAAK,MAAM,SAAS,OAAO,IAAI,CAAC,MAAO,OACrC;AAIR;;;;;;;;;;;;;;;;AAiBA,SAAgB,UAAa,OAAY,MAAc,IAAY;CACjE,MAAM,SAAS,CAAC,GAAG,KAAK;CAExB,IAAI,OAAO,KAAK,QAAQ,OAAO,UAAU,KAAK,KAAK,MAAM,OAAO,QAC9D,OAAO;CAGT,MAAM,OAAO,OAAO;CAEpB,OAAO,OAAO,MAAM,CAAC;CACrB,OAAO,OAAO,IAAI,GAAG,IAAI;CAEzB,OAAO;AACT;AAqGA,SAAgB,UAAa,UAAe,UAAe,SAA8C;CACvG,MAAM,EAAE,cAAc,SAAY,SAAS,WAAW,CAAC;CAKvD,MAAM,gBAAgB,KAAyB,SAAY;EACzD,MAAM,MAAM,WAAW,IAAI;EAC3B,OAAO;GACL;GACA,SAAS,IAAI,IAAI,GAAG,KAAK,CAAC;EAC5B;CACF;CAEA,MAAM,YAAY,QAAa;EAC7B,MAAM,sBAAM,IAAI,IAAmB;EAEnC,IAAI,SAAS,MAAM,UAAU;GAC3B,MAAM,EAAE,KAAK,YAAY,aAAa,KAAK,IAAI;GAC/C,QAAQ,KAAK,KAAK;GAClB,IAAI,IAAI,KAAK,OAAO;EACtB,CAAC;EAED,OAAO;CACT;CAEA,MAAM,gBAAgB,KAAU,YAAsB;EACpD,OAAO,QAAQ,KAAK,UAAU,IAAI,MAAM;CAC1C;CAEA,MAAM,SAAS,SAAS,QAAQ;CAChC,MAAM,SAAS,SAAS,QAAQ;CAChC,MAAM,4BAAY,IAAI,IAAS;CAC/B,MAAM,yBAAS,IAAI,IAAS;CAC5B,MAAM,2BAAW,IAAI,IAAS;CAE9B,KAAK,MAAM,OAAO,OAAO,KAAK,GAC5B,IAAI,OAAO,IAAI,GAAG,GAChB,SAAS,IAAI,GAAG;MAEhB,UAAU,IAAI,GAAG;CAIrB,KAAK,MAAM,OAAO,OAAO,KAAK,GAC5B,IAAI,CAAC,OAAO,IAAI,GAAG,GACjB,OAAO,IAAI,GAAG;CAIlB,OAAO;EACL,SAAS,CAAC,GAAG,SAAS,EAAE,KAAK,QAAQ;GACnC,MAAM,UAAU,OAAO,IAAI,GAAG,KAAK,CAAC;GACpC,OAAO;IACL,YAAY;IACZ,WAAW,aAAa,UAAU,OAAO;GAC3C;EACF,CAAC;EAED,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,QAAQ;GAC7B,MAAM,UAAU,OAAO,IAAI,GAAG,KAAK,CAAC;GACpC,OAAO;IACL,YAAY;IACZ,WAAW,aAAa,UAAU,OAAO;GAC3C;EACF,CAAC;EAED,QAAQ,CAAC,GAAG,QAAQ,EAAE,KAAK,QAAQ;GACjC,MAAM,aAAa,OAAO,IAAI,GAAG,KAAK,CAAC;GACvC,MAAM,aAAa,OAAO,IAAI,GAAG,KAAK,CAAC;GACvC,OAAO;IACL;IACA;IACA,WAAW,aAAa,UAAU,UAAU;IAC5C,WAAW,aAAa,UAAU,UAAU;GAC9C;EACF,CAAC;CACH;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,YAAe,OAAY,SAAmB;CAC5D,OAAO,MAAM,QAAQ,OAAO,UAAU,CAAC,QAAQ,SAAS,KAAK,CAAC;AAChE"}
1
+ {"version":3,"file":"array.cjs","names":[],"sources":["../src/array.ts"],"sourcesContent":["import { isArray, isObject } from './type';\nimport type { MaybePromise } from './types';\n\n/**\n * 检查给定的值是否为类数组对象。\n *\n * 类数组对象是指具有 `length` 属性且 `length` 属性为非负数的对象。\n *\n * @param unknown - 要检查的值。\n * @returns 如果值是类数组对象,则返回 `true`,否则返回 `false`。\n */\nexport function isArrayLike(unknown: unknown) {\n if (isArray(unknown)) return true;\n\n if (isObject(unknown)) {\n const arrayLike = unknown as { length: unknown };\n return typeof arrayLike.length === 'number' && arrayLike.length >= 0;\n }\n\n return false;\n}\n\n/**\n * 从数组中选择指定索引的元素。\n *\n * @param array - 要从中选择元素的数组。\n * @param indexes - 要选择的元素的索引数组。\n * @returns 包含指定索引元素的新数组。\n */\nexport function arrayPick<T>(array: T[], indexes: number[]) {\n const indexes2 = [...indexes];\n return array.filter((_, i) => {\n const index = indexes2.indexOf(i);\n if (index === -1) return false;\n indexes2.splice(index, 1);\n return true;\n });\n}\n\n/**\n * 从数组中排除指定索引的元素。\n *\n * @param array - 要从中排除元素的数组。\n * @param indexes - 要排除的元素的索引数组。\n * @returns 包含排除指定索引元素后的新数组。\n */\nexport function arrayOmit<T>(array: T[], indexes: number[]) {\n const indexes2 = [...indexes];\n return array.filter((_, i) => {\n const index = indexes2.indexOf(i);\n if (index === -1) return true;\n indexes2.splice(index, 1);\n return false;\n });\n}\n\n/**\n * 遍历数组中的每个元素,并对每个元素执行提供的回调函数。\n *\n * @param array - 要遍历的数组。\n * @param iterator - 对每个元素执行的回调函数。如果回调函数返回 `false`,则提前终止遍历。\n * @param reverse - 是否以相反的顺序遍历数组。默认为 `false`。\n * @returns 无返回值。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3];\n * arrayEach(arr, (item, index) => {\n * console.log(item, index);\n * if (index === 1) return false; // 提前终止遍历\n * });\n * ```\n */\nexport function arrayEach<T>(array: T[], iterator: (item: T, index: number) => false | unknown, reverse = false) {\n const _array = [...array];\n const length = array.length;\n\n if (reverse) {\n for (let i = length - 1; i >= 0; i--) {\n if (iterator(_array[i], i) === false) {\n break;\n }\n }\n } else {\n for (let i = 0; i < length; i++) {\n if (iterator(_array[i], i) === false) {\n break;\n }\n }\n }\n}\n\n/**\n * 异步遍历数组中的每个元素,并对每个元素执行提供的回调函数。\n *\n * @param array - 要遍历的数组。\n * @param iterator - 对每个元素执行的异步回调函数。如果回调函数返回 `false`,则提前终止遍历。\n * @param reverse - 是否以相反的顺序遍历数组。默认为 `false`。\n * @returns 无返回值。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3];\n * await arrayEachAsync(arr, async (item, index) => {\n * await someAsyncOperation(item);\n * if (index === 1) return false; // 提前终止遍历\n * });\n * ```\n */\nexport async function arrayEachAsync<T>(\n array: T[],\n iterator: (item: T, index: number) => MaybePromise<false | unknown>,\n reverse = false,\n) {\n const _array = [...array];\n const length = array.length;\n\n if (reverse) {\n for (let i = length - 1; i >= 0; i--) {\n if ((await iterator(_array[i], i)) === false) {\n break;\n }\n }\n } else {\n for (let i = 0; i < length; i++) {\n if ((await iterator(_array[i], i)) === false) {\n break;\n }\n }\n }\n}\n\n/**\n * 将数组中的元素移动到指定位置。\n *\n * @param array - 要移动元素的数组。\n * @param from - 要移动的元素的起始位置。\n * @param to - 要移动的元素的目标位置。\n * @returns 新的数组,其中包含移动后的元素。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3, 4, 5];\n * const newArr = arrayMove(arr, 1, 3);\n * // 返回 [1, 3, 4, 2, 5]\n * ```\n */\nexport function arrayMove<T>(array: T[], from: number, to: number) {\n const array2 = [...array];\n\n if (from < 0 || from >= array2.length || to < 0 || to >= array2.length) {\n return array2;\n }\n\n const item = array2[from];\n\n array2.splice(from, 1);\n array2.splice(to, 0, item);\n\n return array2;\n}\n\n/**\n * 比较两个数组的差异,返回包含删除、新增和相同元素信息的对象\n *\n * @template T - 数组元素的类型\n * @param {T[]} refArray - 参考数组(原始数组)\n * @param {T[]} curArray - 当前数组(比较数组)\n * @returns {ArrayDiffs<T>} 包含差异信息的对象\n *\n * @example\n * ```typescript\n * const ref = [1, 2, 3];\n * const cur = [2, 3, 4];\n * const diff = arrayDiff(ref, cur);\n * // 返回结果:\n * // {\n * // deletes: [{refIndexes: [0], refValue: 1}],\n * // adds: [{curIndexes: [2], curValue: 4}],\n * // equals: [\n * // {refIndexes: [1], curIndexes: [0], refValue: 2, curValue: 2},\n * // {refIndexes: [2], curIndexes: [1], refValue: 3, curValue: 3}\n * // ]\n * // }\n * ```\n */\nexport type ArrayDiffs<T> = {\n /**\n * 被删除的元素列表\n * @type {Array}\n * @property {number[]} refIndexes - 元素在参考数组中的所有索引位置\n * @property {T} refValue - 被删除的元素值\n */\n deletes: {\n /**\n * 元素在参考数组中的所有索引位置\n * @type {number[]}\n */\n refIndexes: number[];\n /**\n * 被删除的元素值\n * @type {T[]}\n */\n refValues: T[];\n }[];\n\n /**\n * 新增的元素列表\n * @type {Array}\n * @property {number[]} curIndexes - 元素在当前数组中的所有索引位置\n * @property {T} curValue - 新增的元素值\n */\n adds: {\n /**\n * 元素在当前数组中的所有索引位置\n * @type {number[]}\n */\n curIndexes: number[];\n /**\n * 新增的元素值\n * @type {T[]}\n */\n curValues: T[];\n }[];\n\n /**\n * 相同的元素列表\n * @type {Array}\n * @property {number[]} refIndexes - 元素在参考数组中的所有索引位置\n * @property {number[]} curIndexes - 元素在当前数组中的所有索引位置\n * @property {T} refValue - 参考数组中的元素值\n * @property {T} curValue - 当前数组中的元素值\n */\n equals: {\n /**\n * 元素在参考数组中的所有索引位置\n * @type {number[]}\n */\n refIndexes: number[];\n /**\n * 元素在当前数组中的所有索引位置\n * @type {number[]}\n */\n curIndexes: number[];\n /**\n * 参考数组中的元素值\n * @type {T[]}\n */\n refValues: T[];\n /**\n * 当前数组中的元素值\n * @type {T[]}\n */\n curValues: T[];\n }[];\n};\n\nexport type ArrayDiffOptions<T> = {\n getItemKey: (item: T) => unknown;\n};\n\nexport function arrayDiff<T>(refArray: T[], curArray: T[], options?: ArrayDiffOptions<T>): ArrayDiffs<T> {\n const { getItemKey = (item: T) => item } = options || {};\n\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n type Key = any;\n\n const toKeyIndexes = (map: Map<Key, number[]>, item: T) => {\n const key = getItemKey(item);\n return {\n key,\n indexes: map.get(key) || [],\n };\n };\n\n const buildMap = (arr: T[]) => {\n const map = new Map<Key, number[]>();\n\n arr.forEach((item, index) => {\n const { key, indexes } = toKeyIndexes(map, item);\n indexes.push(index);\n map.set(key, indexes);\n });\n\n return map;\n };\n\n const toIndexesArr = (arr: T[], indexes: number[]) => {\n return indexes.map((index) => arr[index]);\n };\n\n const refMap = buildMap(refArray);\n const curMap = buildMap(curArray);\n const deleteSet = new Set<Key>();\n const addSet = new Set<Key>();\n const equalSet = new Set<Key>();\n\n for (const key of refMap.keys()) {\n if (curMap.has(key)) {\n equalSet.add(key);\n } else {\n deleteSet.add(key);\n }\n }\n\n for (const key of curMap.keys()) {\n if (!refMap.has(key)) {\n addSet.add(key);\n }\n }\n\n return {\n deletes: [...deleteSet].map((key) => {\n const indexes = refMap.get(key) || [];\n return {\n refIndexes: indexes,\n refValues: toIndexesArr(refArray, indexes),\n };\n }),\n\n adds: [...addSet].map((key) => {\n const indexes = curMap.get(key) || [];\n return {\n curIndexes: indexes,\n curValues: toIndexesArr(curArray, indexes),\n };\n }),\n\n equals: [...equalSet].map((key) => {\n const refIndexes = refMap.get(key) || [];\n const curIndexes = curMap.get(key) || [];\n return {\n refIndexes,\n curIndexes,\n refValues: toIndexesArr(refArray, refIndexes),\n curValues: toIndexesArr(curArray, curIndexes),\n };\n }),\n };\n}\n\n/**\n * 从数组中移除指定索引的元素\n * @template T - 数组元素的类型\n * @param {T[]} array - 原始数组\n * @param {number[]} indexes - 要移除的元素索引数组\n * @returns {T[]} 移除指定索引元素后的新数组\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3, 4, 5];\n * const newArr = arrayRemove(arr, [1, 3]);\n * // 返回结果: [1, 3, 5]\n * ```\n */\nexport function arrayRemove<T>(array: T[], indexes: number[]) {\n return array.filter((_item, index) => !indexes.includes(index));\n}\n\nexport type ArraySampleOptions = {\n /**\n * 取样数量,默认 1。\n * 小数向下取整,负数归零。\n */\n count?: number;\n /**\n * 是否保持原数组顺序。\n * 为 `true` 时,结果元素按原数组中的相对顺序排列。\n * 为 `false` 时,结果顺序随机。\n * @default false\n */\n ordered?: boolean;\n /**\n * 是否有放回取样。\n * 为 `true` 时,同一元素可能被多次选中。\n * 为 `false` 时,每个元素至多被选中一次。\n * @default false\n */\n replacement?: boolean;\n};\n\n/**\n * 从数组中随机取样指定数量的元素。\n *\n * @template T - 数组元素的类型\n * @param array - 要取样的数组。\n * @param options - 取样选项。\n * @returns 包含取样结果的新数组。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3, 4, 5];\n * arraySample(arr);\n * // => [3](随机取 1 个)\n *\n * arraySample(arr, { count: 3 });\n * // => [5, 1, 3](无序、无放回取 3 个)\n *\n * arraySample(arr, { count: 3, ordered: true });\n * // => [1, 3, 4](保持顺序、无放回)\n *\n * arraySample(arr, { count: 3, replacement: true });\n * // => [4, 2, 4](无序、有放回,可能重复)\n * ```\n */\nexport function arraySample<T>(array: T[], options?: ArraySampleOptions): T[] {\n const { count = 1, ordered = false, replacement = false } = options || {};\n\n let _count = Math.floor(count);\n if (_count < 0) _count = 0;\n\n const length = array.length;\n\n if (length === 0 || _count === 0) return [];\n\n // 有放回取样\n if (replacement) {\n const indices: number[] = [];\n\n for (let i = 0; i < _count; i++) {\n indices.push(Math.floor(Math.random() * length));\n }\n\n if (ordered) {\n indices.sort((a, b) => a - b);\n }\n\n return indices.map((i) => array[i]);\n }\n\n // 无放回取样:Fisher-Yates 部分洗牌,取前 n 个\n const n = Math.min(_count, length);\n const indices = Array.from({ length }, (_, i) => i);\n\n for (let i = 0; i < n; i++) {\n const j = i + Math.floor(Math.random() * (length - i));\n [indices[i], indices[j]] = [indices[j], indices[i]];\n }\n\n const selected = indices.slice(0, n);\n\n if (ordered) {\n selected.sort((a, b) => a - b);\n }\n\n return selected.map((i) => array[i]);\n}\n"],"mappings":"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"}
package/dist/array.d.ts CHANGED
@@ -187,3 +187,49 @@ export declare function arrayDiff<T>(refArray: T[], curArray: T[], options?: Arr
187
187
  * ```
188
188
  */
189
189
  export declare function arrayRemove<T>(array: T[], indexes: number[]): T[];
190
+ export type ArraySampleOptions = {
191
+ /**
192
+ * 取样数量,默认 1。
193
+ * 小数向下取整,负数归零。
194
+ */
195
+ count?: number;
196
+ /**
197
+ * 是否保持原数组顺序。
198
+ * 为 `true` 时,结果元素按原数组中的相对顺序排列。
199
+ * 为 `false` 时,结果顺序随机。
200
+ * @default false
201
+ */
202
+ ordered?: boolean;
203
+ /**
204
+ * 是否有放回取样。
205
+ * 为 `true` 时,同一元素可能被多次选中。
206
+ * 为 `false` 时,每个元素至多被选中一次。
207
+ * @default false
208
+ */
209
+ replacement?: boolean;
210
+ };
211
+ /**
212
+ * 从数组中随机取样指定数量的元素。
213
+ *
214
+ * @template T - 数组元素的类型
215
+ * @param array - 要取样的数组。
216
+ * @param options - 取样选项。
217
+ * @returns 包含取样结果的新数组。
218
+ *
219
+ * @example
220
+ * ```typescript
221
+ * const arr = [1, 2, 3, 4, 5];
222
+ * arraySample(arr);
223
+ * // => [3](随机取 1 个)
224
+ *
225
+ * arraySample(arr, { count: 3 });
226
+ * // => [5, 1, 3](无序、无放回取 3 个)
227
+ *
228
+ * arraySample(arr, { count: 3, ordered: true });
229
+ * // => [1, 3, 4](保持顺序、无放回)
230
+ *
231
+ * arraySample(arr, { count: 3, replacement: true });
232
+ * // => [4, 2, 4](无序、有放回,可能重复)
233
+ * ```
234
+ */
235
+ export declare function arraySample<T>(array: T[], options?: ArraySampleOptions): T[];
package/dist/array.mjs CHANGED
@@ -192,7 +192,53 @@ function arrayDiff(refArray, curArray, options) {
192
192
  function arrayRemove(array, indexes) {
193
193
  return array.filter((_item, index) => !indexes.includes(index));
194
194
  }
195
+ /**
196
+ * 从数组中随机取样指定数量的元素。
197
+ *
198
+ * @template T - 数组元素的类型
199
+ * @param array - 要取样的数组。
200
+ * @param options - 取样选项。
201
+ * @returns 包含取样结果的新数组。
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * const arr = [1, 2, 3, 4, 5];
206
+ * arraySample(arr);
207
+ * // => [3](随机取 1 个)
208
+ *
209
+ * arraySample(arr, { count: 3 });
210
+ * // => [5, 1, 3](无序、无放回取 3 个)
211
+ *
212
+ * arraySample(arr, { count: 3, ordered: true });
213
+ * // => [1, 3, 4](保持顺序、无放回)
214
+ *
215
+ * arraySample(arr, { count: 3, replacement: true });
216
+ * // => [4, 2, 4](无序、有放回,可能重复)
217
+ * ```
218
+ */
219
+ function arraySample(array, options) {
220
+ const { count = 1, ordered = false, replacement = false } = options || {};
221
+ let _count = Math.floor(count);
222
+ if (_count < 0) _count = 0;
223
+ const length = array.length;
224
+ if (length === 0 || _count === 0) return [];
225
+ if (replacement) {
226
+ const indices = [];
227
+ for (let i = 0; i < _count; i++) indices.push(Math.floor(Math.random() * length));
228
+ if (ordered) indices.sort((a, b) => a - b);
229
+ return indices.map((i) => array[i]);
230
+ }
231
+ const n = Math.min(_count, length);
232
+ const indices = Array.from({ length }, (_, i) => i);
233
+ for (let i = 0; i < n; i++) {
234
+ const j = i + Math.floor(Math.random() * (length - i));
235
+ [indices[i], indices[j]] = [indices[j], indices[i]];
236
+ }
237
+ const selected = indices.slice(0, n);
238
+ if (ordered) selected.sort((a, b) => a - b);
239
+ return selected.map((i) => array[i]);
240
+ }
195
241
  //#endregion
196
- export { arrayDiff, arrayEach, arrayEachAsync, arrayMove, arrayOmit, arrayPick, arrayRemove, isArrayLike };
242
+ export { arrayDiff, arrayEach, arrayEachAsync, arrayMove, arrayOmit, arrayPick, arrayRemove, arraySample, isArrayLike };
197
243
 
198
244
  //# sourceMappingURL=array.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"array.mjs","names":[],"sources":["../src/array.ts"],"sourcesContent":["import { isArray, isObject } from './type';\nimport type { MaybePromise } from './types';\n\n/**\n * 检查给定的值是否为类数组对象。\n *\n * 类数组对象是指具有 `length` 属性且 `length` 属性为非负数的对象。\n *\n * @param unknown - 要检查的值。\n * @returns 如果值是类数组对象,则返回 `true`,否则返回 `false`。\n */\nexport function isArrayLike(unknown: unknown) {\n if (isArray(unknown)) return true;\n\n if (isObject(unknown)) {\n const arrayLike = unknown as { length: unknown };\n return typeof arrayLike.length === 'number' && arrayLike.length >= 0;\n }\n\n return false;\n}\n\n/**\n * 从数组中选择指定索引的元素。\n *\n * @param array - 要从中选择元素的数组。\n * @param indexes - 要选择的元素的索引数组。\n * @returns 包含指定索引元素的新数组。\n */\nexport function arrayPick<T>(array: T[], indexes: number[]) {\n const indexes2 = [...indexes];\n return array.filter((_, i) => {\n const index = indexes2.indexOf(i);\n if (index === -1) return false;\n indexes2.splice(index, 1);\n return true;\n });\n}\n\n/**\n * 从数组中排除指定索引的元素。\n *\n * @param array - 要从中排除元素的数组。\n * @param indexes - 要排除的元素的索引数组。\n * @returns 包含排除指定索引元素后的新数组。\n */\nexport function arrayOmit<T>(array: T[], indexes: number[]) {\n const indexes2 = [...indexes];\n return array.filter((_, i) => {\n const index = indexes2.indexOf(i);\n if (index === -1) return true;\n indexes2.splice(index, 1);\n return false;\n });\n}\n\n/**\n * 遍历数组中的每个元素,并对每个元素执行提供的回调函数。\n *\n * @param array - 要遍历的数组。\n * @param iterator - 对每个元素执行的回调函数。如果回调函数返回 `false`,则提前终止遍历。\n * @param reverse - 是否以相反的顺序遍历数组。默认为 `false`。\n * @returns 无返回值。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3];\n * arrayEach(arr, (item, index) => {\n * console.log(item, index);\n * if (index === 1) return false; // 提前终止遍历\n * });\n * ```\n */\nexport function arrayEach<T>(array: T[], iterator: (item: T, index: number) => false | unknown, reverse = false) {\n const _array = [...array];\n const length = array.length;\n\n if (reverse) {\n for (let i = length - 1; i >= 0; i--) {\n if (iterator(_array[i], i) === false) {\n break;\n }\n }\n } else {\n for (let i = 0; i < length; i++) {\n if (iterator(_array[i], i) === false) {\n break;\n }\n }\n }\n}\n\n/**\n * 异步遍历数组中的每个元素,并对每个元素执行提供的回调函数。\n *\n * @param array - 要遍历的数组。\n * @param iterator - 对每个元素执行的异步回调函数。如果回调函数返回 `false`,则提前终止遍历。\n * @param reverse - 是否以相反的顺序遍历数组。默认为 `false`。\n * @returns 无返回值。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3];\n * await arrayEachAsync(arr, async (item, index) => {\n * await someAsyncOperation(item);\n * if (index === 1) return false; // 提前终止遍历\n * });\n * ```\n */\nexport async function arrayEachAsync<T>(\n array: T[],\n iterator: (item: T, index: number) => MaybePromise<false | unknown>,\n reverse = false,\n) {\n const _array = [...array];\n const length = array.length;\n\n if (reverse) {\n for (let i = length - 1; i >= 0; i--) {\n if ((await iterator(_array[i], i)) === false) {\n break;\n }\n }\n } else {\n for (let i = 0; i < length; i++) {\n if ((await iterator(_array[i], i)) === false) {\n break;\n }\n }\n }\n}\n\n/**\n * 将数组中的元素移动到指定位置。\n *\n * @param array - 要移动元素的数组。\n * @param from - 要移动的元素的起始位置。\n * @param to - 要移动的元素的目标位置。\n * @returns 新的数组,其中包含移动后的元素。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3, 4, 5];\n * const newArr = arrayMove(arr, 1, 3);\n * // 返回 [1, 3, 4, 2, 5]\n * ```\n */\nexport function arrayMove<T>(array: T[], from: number, to: number) {\n const array2 = [...array];\n\n if (from < 0 || from >= array2.length || to < 0 || to >= array2.length) {\n return array2;\n }\n\n const item = array2[from];\n\n array2.splice(from, 1);\n array2.splice(to, 0, item);\n\n return array2;\n}\n\n/**\n * 比较两个数组的差异,返回包含删除、新增和相同元素信息的对象\n *\n * @template T - 数组元素的类型\n * @param {T[]} refArray - 参考数组(原始数组)\n * @param {T[]} curArray - 当前数组(比较数组)\n * @returns {ArrayDiffs<T>} 包含差异信息的对象\n *\n * @example\n * ```typescript\n * const ref = [1, 2, 3];\n * const cur = [2, 3, 4];\n * const diff = arrayDiff(ref, cur);\n * // 返回结果:\n * // {\n * // deletes: [{refIndexes: [0], refValue: 1}],\n * // adds: [{curIndexes: [2], curValue: 4}],\n * // equals: [\n * // {refIndexes: [1], curIndexes: [0], refValue: 2, curValue: 2},\n * // {refIndexes: [2], curIndexes: [1], refValue: 3, curValue: 3}\n * // ]\n * // }\n * ```\n */\nexport type ArrayDiffs<T> = {\n /**\n * 被删除的元素列表\n * @type {Array}\n * @property {number[]} refIndexes - 元素在参考数组中的所有索引位置\n * @property {T} refValue - 被删除的元素值\n */\n deletes: {\n /**\n * 元素在参考数组中的所有索引位置\n * @type {number[]}\n */\n refIndexes: number[];\n /**\n * 被删除的元素值\n * @type {T[]}\n */\n refValues: T[];\n }[];\n\n /**\n * 新增的元素列表\n * @type {Array}\n * @property {number[]} curIndexes - 元素在当前数组中的所有索引位置\n * @property {T} curValue - 新增的元素值\n */\n adds: {\n /**\n * 元素在当前数组中的所有索引位置\n * @type {number[]}\n */\n curIndexes: number[];\n /**\n * 新增的元素值\n * @type {T[]}\n */\n curValues: T[];\n }[];\n\n /**\n * 相同的元素列表\n * @type {Array}\n * @property {number[]} refIndexes - 元素在参考数组中的所有索引位置\n * @property {number[]} curIndexes - 元素在当前数组中的所有索引位置\n * @property {T} refValue - 参考数组中的元素值\n * @property {T} curValue - 当前数组中的元素值\n */\n equals: {\n /**\n * 元素在参考数组中的所有索引位置\n * @type {number[]}\n */\n refIndexes: number[];\n /**\n * 元素在当前数组中的所有索引位置\n * @type {number[]}\n */\n curIndexes: number[];\n /**\n * 参考数组中的元素值\n * @type {T[]}\n */\n refValues: T[];\n /**\n * 当前数组中的元素值\n * @type {T[]}\n */\n curValues: T[];\n }[];\n};\n\nexport type ArrayDiffOptions<T> = {\n getItemKey: (item: T) => unknown;\n};\n\nexport function arrayDiff<T>(refArray: T[], curArray: T[], options?: ArrayDiffOptions<T>): ArrayDiffs<T> {\n const { getItemKey = (item: T) => item } = options || {};\n\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n type Key = any;\n\n const toKeyIndexes = (map: Map<Key, number[]>, item: T) => {\n const key = getItemKey(item);\n return {\n key,\n indexes: map.get(key) || [],\n };\n };\n\n const buildMap = (arr: T[]) => {\n const map = new Map<Key, number[]>();\n\n arr.forEach((item, index) => {\n const { key, indexes } = toKeyIndexes(map, item);\n indexes.push(index);\n map.set(key, indexes);\n });\n\n return map;\n };\n\n const toIndexesArr = (arr: T[], indexes: number[]) => {\n return indexes.map((index) => arr[index]);\n };\n\n const refMap = buildMap(refArray);\n const curMap = buildMap(curArray);\n const deleteSet = new Set<Key>();\n const addSet = new Set<Key>();\n const equalSet = new Set<Key>();\n\n for (const key of refMap.keys()) {\n if (curMap.has(key)) {\n equalSet.add(key);\n } else {\n deleteSet.add(key);\n }\n }\n\n for (const key of curMap.keys()) {\n if (!refMap.has(key)) {\n addSet.add(key);\n }\n }\n\n return {\n deletes: [...deleteSet].map((key) => {\n const indexes = refMap.get(key) || [];\n return {\n refIndexes: indexes,\n refValues: toIndexesArr(refArray, indexes),\n };\n }),\n\n adds: [...addSet].map((key) => {\n const indexes = curMap.get(key) || [];\n return {\n curIndexes: indexes,\n curValues: toIndexesArr(curArray, indexes),\n };\n }),\n\n equals: [...equalSet].map((key) => {\n const refIndexes = refMap.get(key) || [];\n const curIndexes = curMap.get(key) || [];\n return {\n refIndexes,\n curIndexes,\n refValues: toIndexesArr(refArray, refIndexes),\n curValues: toIndexesArr(curArray, curIndexes),\n };\n }),\n };\n}\n\n/**\n * 从数组中移除指定索引的元素\n * @template T - 数组元素的类型\n * @param {T[]} array - 原始数组\n * @param {number[]} indexes - 要移除的元素索引数组\n * @returns {T[]} 移除指定索引元素后的新数组\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3, 4, 5];\n * const newArr = arrayRemove(arr, [1, 3]);\n * // 返回结果: [1, 3, 5]\n * ```\n */\nexport function arrayRemove<T>(array: T[], indexes: number[]) {\n return array.filter((_item, index) => !indexes.includes(index));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"array.mjs","names":[],"sources":["../src/array.ts"],"sourcesContent":["import { isArray, isObject } from './type';\nimport type { MaybePromise } from './types';\n\n/**\n * 检查给定的值是否为类数组对象。\n *\n * 类数组对象是指具有 `length` 属性且 `length` 属性为非负数的对象。\n *\n * @param unknown - 要检查的值。\n * @returns 如果值是类数组对象,则返回 `true`,否则返回 `false`。\n */\nexport function isArrayLike(unknown: unknown) {\n if (isArray(unknown)) return true;\n\n if (isObject(unknown)) {\n const arrayLike = unknown as { length: unknown };\n return typeof arrayLike.length === 'number' && arrayLike.length >= 0;\n }\n\n return false;\n}\n\n/**\n * 从数组中选择指定索引的元素。\n *\n * @param array - 要从中选择元素的数组。\n * @param indexes - 要选择的元素的索引数组。\n * @returns 包含指定索引元素的新数组。\n */\nexport function arrayPick<T>(array: T[], indexes: number[]) {\n const indexes2 = [...indexes];\n return array.filter((_, i) => {\n const index = indexes2.indexOf(i);\n if (index === -1) return false;\n indexes2.splice(index, 1);\n return true;\n });\n}\n\n/**\n * 从数组中排除指定索引的元素。\n *\n * @param array - 要从中排除元素的数组。\n * @param indexes - 要排除的元素的索引数组。\n * @returns 包含排除指定索引元素后的新数组。\n */\nexport function arrayOmit<T>(array: T[], indexes: number[]) {\n const indexes2 = [...indexes];\n return array.filter((_, i) => {\n const index = indexes2.indexOf(i);\n if (index === -1) return true;\n indexes2.splice(index, 1);\n return false;\n });\n}\n\n/**\n * 遍历数组中的每个元素,并对每个元素执行提供的回调函数。\n *\n * @param array - 要遍历的数组。\n * @param iterator - 对每个元素执行的回调函数。如果回调函数返回 `false`,则提前终止遍历。\n * @param reverse - 是否以相反的顺序遍历数组。默认为 `false`。\n * @returns 无返回值。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3];\n * arrayEach(arr, (item, index) => {\n * console.log(item, index);\n * if (index === 1) return false; // 提前终止遍历\n * });\n * ```\n */\nexport function arrayEach<T>(array: T[], iterator: (item: T, index: number) => false | unknown, reverse = false) {\n const _array = [...array];\n const length = array.length;\n\n if (reverse) {\n for (let i = length - 1; i >= 0; i--) {\n if (iterator(_array[i], i) === false) {\n break;\n }\n }\n } else {\n for (let i = 0; i < length; i++) {\n if (iterator(_array[i], i) === false) {\n break;\n }\n }\n }\n}\n\n/**\n * 异步遍历数组中的每个元素,并对每个元素执行提供的回调函数。\n *\n * @param array - 要遍历的数组。\n * @param iterator - 对每个元素执行的异步回调函数。如果回调函数返回 `false`,则提前终止遍历。\n * @param reverse - 是否以相反的顺序遍历数组。默认为 `false`。\n * @returns 无返回值。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3];\n * await arrayEachAsync(arr, async (item, index) => {\n * await someAsyncOperation(item);\n * if (index === 1) return false; // 提前终止遍历\n * });\n * ```\n */\nexport async function arrayEachAsync<T>(\n array: T[],\n iterator: (item: T, index: number) => MaybePromise<false | unknown>,\n reverse = false,\n) {\n const _array = [...array];\n const length = array.length;\n\n if (reverse) {\n for (let i = length - 1; i >= 0; i--) {\n if ((await iterator(_array[i], i)) === false) {\n break;\n }\n }\n } else {\n for (let i = 0; i < length; i++) {\n if ((await iterator(_array[i], i)) === false) {\n break;\n }\n }\n }\n}\n\n/**\n * 将数组中的元素移动到指定位置。\n *\n * @param array - 要移动元素的数组。\n * @param from - 要移动的元素的起始位置。\n * @param to - 要移动的元素的目标位置。\n * @returns 新的数组,其中包含移动后的元素。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3, 4, 5];\n * const newArr = arrayMove(arr, 1, 3);\n * // 返回 [1, 3, 4, 2, 5]\n * ```\n */\nexport function arrayMove<T>(array: T[], from: number, to: number) {\n const array2 = [...array];\n\n if (from < 0 || from >= array2.length || to < 0 || to >= array2.length) {\n return array2;\n }\n\n const item = array2[from];\n\n array2.splice(from, 1);\n array2.splice(to, 0, item);\n\n return array2;\n}\n\n/**\n * 比较两个数组的差异,返回包含删除、新增和相同元素信息的对象\n *\n * @template T - 数组元素的类型\n * @param {T[]} refArray - 参考数组(原始数组)\n * @param {T[]} curArray - 当前数组(比较数组)\n * @returns {ArrayDiffs<T>} 包含差异信息的对象\n *\n * @example\n * ```typescript\n * const ref = [1, 2, 3];\n * const cur = [2, 3, 4];\n * const diff = arrayDiff(ref, cur);\n * // 返回结果:\n * // {\n * // deletes: [{refIndexes: [0], refValue: 1}],\n * // adds: [{curIndexes: [2], curValue: 4}],\n * // equals: [\n * // {refIndexes: [1], curIndexes: [0], refValue: 2, curValue: 2},\n * // {refIndexes: [2], curIndexes: [1], refValue: 3, curValue: 3}\n * // ]\n * // }\n * ```\n */\nexport type ArrayDiffs<T> = {\n /**\n * 被删除的元素列表\n * @type {Array}\n * @property {number[]} refIndexes - 元素在参考数组中的所有索引位置\n * @property {T} refValue - 被删除的元素值\n */\n deletes: {\n /**\n * 元素在参考数组中的所有索引位置\n * @type {number[]}\n */\n refIndexes: number[];\n /**\n * 被删除的元素值\n * @type {T[]}\n */\n refValues: T[];\n }[];\n\n /**\n * 新增的元素列表\n * @type {Array}\n * @property {number[]} curIndexes - 元素在当前数组中的所有索引位置\n * @property {T} curValue - 新增的元素值\n */\n adds: {\n /**\n * 元素在当前数组中的所有索引位置\n * @type {number[]}\n */\n curIndexes: number[];\n /**\n * 新增的元素值\n * @type {T[]}\n */\n curValues: T[];\n }[];\n\n /**\n * 相同的元素列表\n * @type {Array}\n * @property {number[]} refIndexes - 元素在参考数组中的所有索引位置\n * @property {number[]} curIndexes - 元素在当前数组中的所有索引位置\n * @property {T} refValue - 参考数组中的元素值\n * @property {T} curValue - 当前数组中的元素值\n */\n equals: {\n /**\n * 元素在参考数组中的所有索引位置\n * @type {number[]}\n */\n refIndexes: number[];\n /**\n * 元素在当前数组中的所有索引位置\n * @type {number[]}\n */\n curIndexes: number[];\n /**\n * 参考数组中的元素值\n * @type {T[]}\n */\n refValues: T[];\n /**\n * 当前数组中的元素值\n * @type {T[]}\n */\n curValues: T[];\n }[];\n};\n\nexport type ArrayDiffOptions<T> = {\n getItemKey: (item: T) => unknown;\n};\n\nexport function arrayDiff<T>(refArray: T[], curArray: T[], options?: ArrayDiffOptions<T>): ArrayDiffs<T> {\n const { getItemKey = (item: T) => item } = options || {};\n\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n type Key = any;\n\n const toKeyIndexes = (map: Map<Key, number[]>, item: T) => {\n const key = getItemKey(item);\n return {\n key,\n indexes: map.get(key) || [],\n };\n };\n\n const buildMap = (arr: T[]) => {\n const map = new Map<Key, number[]>();\n\n arr.forEach((item, index) => {\n const { key, indexes } = toKeyIndexes(map, item);\n indexes.push(index);\n map.set(key, indexes);\n });\n\n return map;\n };\n\n const toIndexesArr = (arr: T[], indexes: number[]) => {\n return indexes.map((index) => arr[index]);\n };\n\n const refMap = buildMap(refArray);\n const curMap = buildMap(curArray);\n const deleteSet = new Set<Key>();\n const addSet = new Set<Key>();\n const equalSet = new Set<Key>();\n\n for (const key of refMap.keys()) {\n if (curMap.has(key)) {\n equalSet.add(key);\n } else {\n deleteSet.add(key);\n }\n }\n\n for (const key of curMap.keys()) {\n if (!refMap.has(key)) {\n addSet.add(key);\n }\n }\n\n return {\n deletes: [...deleteSet].map((key) => {\n const indexes = refMap.get(key) || [];\n return {\n refIndexes: indexes,\n refValues: toIndexesArr(refArray, indexes),\n };\n }),\n\n adds: [...addSet].map((key) => {\n const indexes = curMap.get(key) || [];\n return {\n curIndexes: indexes,\n curValues: toIndexesArr(curArray, indexes),\n };\n }),\n\n equals: [...equalSet].map((key) => {\n const refIndexes = refMap.get(key) || [];\n const curIndexes = curMap.get(key) || [];\n return {\n refIndexes,\n curIndexes,\n refValues: toIndexesArr(refArray, refIndexes),\n curValues: toIndexesArr(curArray, curIndexes),\n };\n }),\n };\n}\n\n/**\n * 从数组中移除指定索引的元素\n * @template T - 数组元素的类型\n * @param {T[]} array - 原始数组\n * @param {number[]} indexes - 要移除的元素索引数组\n * @returns {T[]} 移除指定索引元素后的新数组\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3, 4, 5];\n * const newArr = arrayRemove(arr, [1, 3]);\n * // 返回结果: [1, 3, 5]\n * ```\n */\nexport function arrayRemove<T>(array: T[], indexes: number[]) {\n return array.filter((_item, index) => !indexes.includes(index));\n}\n\nexport type ArraySampleOptions = {\n /**\n * 取样数量,默认 1。\n * 小数向下取整,负数归零。\n */\n count?: number;\n /**\n * 是否保持原数组顺序。\n * 为 `true` 时,结果元素按原数组中的相对顺序排列。\n * 为 `false` 时,结果顺序随机。\n * @default false\n */\n ordered?: boolean;\n /**\n * 是否有放回取样。\n * 为 `true` 时,同一元素可能被多次选中。\n * 为 `false` 时,每个元素至多被选中一次。\n * @default false\n */\n replacement?: boolean;\n};\n\n/**\n * 从数组中随机取样指定数量的元素。\n *\n * @template T - 数组元素的类型\n * @param array - 要取样的数组。\n * @param options - 取样选项。\n * @returns 包含取样结果的新数组。\n *\n * @example\n * ```typescript\n * const arr = [1, 2, 3, 4, 5];\n * arraySample(arr);\n * // => [3](随机取 1 个)\n *\n * arraySample(arr, { count: 3 });\n * // => [5, 1, 3](无序、无放回取 3 个)\n *\n * arraySample(arr, { count: 3, ordered: true });\n * // => [1, 3, 4](保持顺序、无放回)\n *\n * arraySample(arr, { count: 3, replacement: true });\n * // => [4, 2, 4](无序、有放回,可能重复)\n * ```\n */\nexport function arraySample<T>(array: T[], options?: ArraySampleOptions): T[] {\n const { count = 1, ordered = false, replacement = false } = options || {};\n\n let _count = Math.floor(count);\n if (_count < 0) _count = 0;\n\n const length = array.length;\n\n if (length === 0 || _count === 0) return [];\n\n // 有放回取样\n if (replacement) {\n const indices: number[] = [];\n\n for (let i = 0; i < _count; i++) {\n indices.push(Math.floor(Math.random() * length));\n }\n\n if (ordered) {\n indices.sort((a, b) => a - b);\n }\n\n return indices.map((i) => array[i]);\n }\n\n // 无放回取样:Fisher-Yates 部分洗牌,取前 n 个\n const n = Math.min(_count, length);\n const indices = Array.from({ length }, (_, i) => i);\n\n for (let i = 0; i < n; i++) {\n const j = i + Math.floor(Math.random() * (length - i));\n [indices[i], indices[j]] = [indices[j], indices[i]];\n }\n\n const selected = indices.slice(0, n);\n\n if (ordered) {\n selected.sort((a, b) => a - b);\n }\n\n return selected.map((i) => array[i]);\n}\n"],"mappings":"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"}
package/dist/function.cjs CHANGED
@@ -149,14 +149,14 @@ function fnOnce(fn) {
149
149
  * ```
150
150
  */
151
151
  function fnRetry(fn, options = {}) {
152
- const { maxAttempts = 3, delay = 0, retryWhen } = options;
152
+ const { maxAttempts = 3, delay = 0, skipRetry } = options;
153
153
  return async function(...args) {
154
154
  let lastError;
155
155
  for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
156
156
  return await fn.apply(this, args);
157
157
  } catch (error) {
158
158
  lastError = error;
159
- if (retryWhen && !retryWhen(error)) break;
159
+ if (skipRetry && skipRetry(error)) break;
160
160
  if (attempt < maxAttempts && delay > 0) await require_promise.promiseDelay(delay);
161
161
  }
162
162
  throw lastError;
@@ -1 +1 @@
1
- {"version":3,"file":"function.cjs","names":[],"sources":["../src/function.ts"],"sourcesContent":["import { promiseDelay } from './promise';\nimport { isNumber } from './type';\nimport type { AnyFunction } from './types';\n\n/**\n * 一个空操作函数,不执行任何操作。\n *\n * @example\n * ```typescript\n * fnNoop(); // 不执行任何操作\n * ```\n */\nexport function fnNoop() {\n //\n}\n\n/**\n * 防抖函数的配置选项。\n */\nexport type DebounceOptions = {\n /**\n * 等待时间(毫秒)。\n */\n wait: number;\n /**\n * 是否在等待开始时立即执行一次\n * @default false\n */\n leading?: boolean;\n};\n\n/**\n * 创建一个防抖函数,该函数会在指定的等待时间后执行,如果在等待时间内再次调用,则重新计时。\n *\n * @param fn - 需要防抖的函数。\n * @param wait - 等待时间(毫秒)或包含 `wait` 和 `leading` 选项的对象。\n * @returns 返回一个防抖函数,该函数具有 `cancel` 方法,用于取消防抖操作。\n *\n * @example\n * ```typescript\n * const debouncedFn = fnDebounce(() => {\n * console.log('Debounced!');\n * }, 100);\n *\n * debouncedFn(); // 不会立即执行\n * debouncedFn(); // 重新计时\n * debouncedFn.cancel(); // 取消防抖操作\n * ```\n */\nexport function fnDebounce<F extends AnyFunction>(fn: F, wait: number | DebounceOptions) {\n const options: DebounceOptions = isNumber(wait) ? { wait } : wait;\n let canceled = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let timer: any;\n let leading = false;\n\n const debounced = function (this: unknown, ...args: Parameters<F>) {\n if (canceled) return;\n\n // 第一次执行\n if (options.leading && !leading) {\n leading = true;\n fn.apply(this, args);\n return;\n }\n\n // 最后一次执行\n clearTimeout(timer);\n timer = setTimeout(() => {\n if (canceled) return;\n\n fn.apply(this, args);\n }, options.wait);\n };\n\n debounced.cancel = () => {\n clearTimeout(timer);\n canceled = true;\n };\n\n return debounced;\n}\n\nexport type ThrottleOptions = {\n /**\n * 等待时间(毫秒)。\n */\n wait: number;\n\n /**\n * 是否在第一次调用时立即执行\n * @default false\n */\n leading?: boolean;\n\n /**\n * 是否在调用结束后等待一段时间再执行\n * @default false\n */\n trailing?: boolean;\n};\n\n/**\n * 创建一个节流函数,该函数会在指定的等待时间内最多执行一次,如果在等待时间内再次调用,则忽略后续调用。\n *\n * @param fn - 需要节流的函数。\n * @param wait - 等待时间(毫秒)或包含 `wait`、`leading` 和 `trailing` 选项的对象。\n * @returns 返回一个节流函数,该函数具有 `cancel` 方法,用于取消节流操作。\n *\n * @example\n * ```typescript\n * const throttledFn = fnThrottle(() => {\n * console.log('Throttled!');\n * }, 100);\n *\n * throttledFn(); // 立即执行\n * throttledFn(); // 忽略\n * throttledFn.cancel(); // 取消节流操作\n * ```\n */\nexport function fnThrottle<F extends AnyFunction>(fn: F, wait: number | ThrottleOptions) {\n const options = isNumber(wait) ? { wait } : wait;\n const waitFinal = options.wait;\n let lastTime = 0;\n let canceled = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let timer: any;\n\n const throttled = function (this: unknown, ...args: Parameters<F>) {\n if (canceled) return;\n\n const now = Date.now();\n let executed = false;\n\n // 第一次执行\n if (options.leading && lastTime === 0) {\n lastTime = now;\n fn.apply(this, args);\n executed = true;\n }\n\n // 中间控频执行\n else if (lastTime > 0 && now - lastTime >= waitFinal) {\n lastTime = now;\n fn.apply(this, args);\n executed = true;\n }\n\n // 首次计时\n else if (lastTime === 0) {\n lastTime = now;\n }\n\n // 最后一次执行,仅在本次未执行时才设置 trailing\n if (options.trailing && !executed) {\n clearTimeout(timer);\n timer = setTimeout(() => {\n fn.apply(this, args);\n }, waitFinal);\n }\n };\n\n throttled.cancel = () => {\n canceled = true;\n clearTimeout(timer);\n };\n\n return throttled;\n}\n\n/**\n * 创建一个只执行一次的函数,无论调用多少次,实际执行的函数体也只会执行一次。\n *\n * @param fn - 需要只执行一次的函数。\n * @returns 返回一个只执行一次的函数,该函数在第一次调用后会缓存结果并返回缓存的结果。\n *\n * @example\n * ```typescript\n * const onceFn = fnOnce(() => {\n * console.log('This will be logged only once.');\n * return 42;\n * });\n *\n * console.log(onceFn()); // 输出: This will be logged only once. 42\n * console.log(onceFn()); // 输出: 42\n * ```\n */\nexport function fnOnce<F extends AnyFunction>(fn: F) {\n let called = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let result: any;\n\n return function (this: unknown, ...args: Parameters<F>) {\n if (!called) {\n called = true;\n result = fn.apply(this, args);\n }\n\n return result;\n };\n}\n\nexport type FnRetryOptions = {\n /**\n * 最大尝试次数\n */\n maxAttempts?: number;\n /**\n * 每次重试之间的延迟时间(毫秒)\n * @default 0\n */\n delay?: number;\n /**\n * 自定义重试条件,返回 true 时触发重试\n * @default 仅在抛出错误时重试\n */\n retryWhen?: (error: unknown) => boolean;\n};\n\n/**\n * 创建一个带重试能力的函数,失败时自动重试直到成功或耗尽次数。\n *\n * @param fn - 需要重试的函数。\n * @param options - 重试配置选项。\n * @returns 返回一个带重试能力的函数。\n *\n * @example\n * ```typescript\n * const fetchData = fnRetry(async () => {\n * return await api.request('/data');\n * }, { maxAttempts: 3, delay: 1000 });\n *\n * // 第1次失败 → 等待1s → 第2次失败 → 等待1s → 第3次成功 → 返回结果\n * const result = await fetchData();\n * ```\n */\nexport function fnRetry<F extends AnyFunction>(fn: F, options: FnRetryOptions = {}) {\n const { maxAttempts = 3, delay = 0, retryWhen } = options;\n\n return async function (this: unknown, ...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> {\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return (await fn.apply(this, args)) as Awaited<ReturnType<F>>;\n } catch (error) {\n lastError = error;\n\n if (retryWhen && !retryWhen(error)) break;\n\n if (attempt < maxAttempts && delay > 0) {\n await promiseDelay(delay);\n }\n }\n }\n\n throw lastError;\n };\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,SAAS,CAEzB;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,WAAkC,IAAO,MAAgC;CACvF,MAAM,UAA2B,aAAA,SAAS,IAAI,IAAI,EAAE,KAAK,IAAI;CAC7D,IAAI,WAAW;CAEf,IAAI;CACJ,IAAI,UAAU;CAEd,MAAM,YAAY,SAAyB,GAAG,MAAqB;EACjE,IAAI,UAAU;EAGd,IAAI,QAAQ,WAAW,CAAC,SAAS;GAC/B,UAAU;GACV,GAAG,MAAM,MAAM,IAAI;GACnB;EACF;EAGA,aAAa,KAAK;EAClB,QAAQ,iBAAiB;GACvB,IAAI,UAAU;GAEd,GAAG,MAAM,MAAM,IAAI;EACrB,GAAG,QAAQ,IAAI;CACjB;CAEA,UAAU,eAAe;EACvB,aAAa,KAAK;EAClB,WAAW;CACb;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,WAAkC,IAAO,MAAgC;CACvF,MAAM,UAAU,aAAA,SAAS,IAAI,IAAI,EAAE,KAAK,IAAI;CAC5C,MAAM,YAAY,QAAQ;CAC1B,IAAI,WAAW;CACf,IAAI,WAAW;CAEf,IAAI;CAEJ,MAAM,YAAY,SAAyB,GAAG,MAAqB;EACjE,IAAI,UAAU;EAEd,MAAM,MAAM,KAAK,IAAI;EACrB,IAAI,WAAW;EAGf,IAAI,QAAQ,WAAW,aAAa,GAAG;GACrC,WAAW;GACX,GAAG,MAAM,MAAM,IAAI;GACnB,WAAW;EACb,OAGK,IAAI,WAAW,KAAK,MAAM,YAAY,WAAW;GACpD,WAAW;GACX,GAAG,MAAM,MAAM,IAAI;GACnB,WAAW;EACb,OAGK,IAAI,aAAa,GACpB,WAAW;EAIb,IAAI,QAAQ,YAAY,CAAC,UAAU;GACjC,aAAa,KAAK;GAClB,QAAQ,iBAAiB;IACvB,GAAG,MAAM,MAAM,IAAI;GACrB,GAAG,SAAS;EACd;CACF;CAEA,UAAU,eAAe;EACvB,WAAW;EACX,aAAa,KAAK;CACpB;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,OAA8B,IAAO;CACnD,IAAI,SAAS;CAEb,IAAI;CAEJ,OAAO,SAAyB,GAAG,MAAqB;EACtD,IAAI,CAAC,QAAQ;GACX,SAAS;GACT,SAAS,GAAG,MAAM,MAAM,IAAI;EAC9B;EAEA,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,QAA+B,IAAO,UAA0B,CAAC,GAAG;CAClF,MAAM,EAAE,cAAc,GAAG,QAAQ,GAAG,cAAc;CAElD,OAAO,eAA+B,GAAG,MAAsD;EAC7F,IAAI;EAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,aAAa,WAC5C,IAAI;GACF,OAAQ,MAAM,GAAG,MAAM,MAAM,IAAI;EACnC,SAAS,OAAO;GACd,YAAY;GAEZ,IAAI,aAAa,CAAC,UAAU,KAAK,GAAG;GAEpC,IAAI,UAAU,eAAe,QAAQ,GACnC,MAAM,gBAAA,aAAa,KAAK;EAE5B;EAGF,MAAM;CACR;AACF"}
1
+ {"version":3,"file":"function.cjs","names":[],"sources":["../src/function.ts"],"sourcesContent":["import { promiseDelay } from './promise';\nimport { isNumber } from './type';\nimport type { AnyFunction } from './types';\n\n/**\n * 一个空操作函数,不执行任何操作。\n *\n * @example\n * ```typescript\n * fnNoop(); // 不执行任何操作\n * ```\n */\nexport function fnNoop() {\n //\n}\n\n/**\n * 防抖函数的配置选项。\n */\nexport type DebounceOptions = {\n /**\n * 等待时间(毫秒)。\n */\n wait: number;\n /**\n * 是否在等待开始时立即执行一次\n * @default false\n */\n leading?: boolean;\n};\n\n/**\n * 创建一个防抖函数,该函数会在指定的等待时间后执行,如果在等待时间内再次调用,则重新计时。\n *\n * @param fn - 需要防抖的函数。\n * @param wait - 等待时间(毫秒)或包含 `wait` 和 `leading` 选项的对象。\n * @returns 返回一个防抖函数,该函数具有 `cancel` 方法,用于取消防抖操作。\n *\n * @example\n * ```typescript\n * const debouncedFn = fnDebounce(() => {\n * console.log('Debounced!');\n * }, 100);\n *\n * debouncedFn(); // 不会立即执行\n * debouncedFn(); // 重新计时\n * debouncedFn.cancel(); // 取消防抖操作\n * ```\n */\nexport function fnDebounce<F extends AnyFunction>(fn: F, wait: number | DebounceOptions) {\n const options: DebounceOptions = isNumber(wait) ? { wait } : wait;\n let canceled = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let timer: any;\n let leading = false;\n\n const debounced = function (this: unknown, ...args: Parameters<F>) {\n if (canceled) return;\n\n // 第一次执行\n if (options.leading && !leading) {\n leading = true;\n fn.apply(this, args);\n return;\n }\n\n // 最后一次执行\n clearTimeout(timer);\n timer = setTimeout(() => {\n if (canceled) return;\n\n fn.apply(this, args);\n }, options.wait);\n };\n\n debounced.cancel = () => {\n clearTimeout(timer);\n canceled = true;\n };\n\n return debounced;\n}\n\nexport type ThrottleOptions = {\n /**\n * 等待时间(毫秒)。\n */\n wait: number;\n\n /**\n * 是否在第一次调用时立即执行\n * @default false\n */\n leading?: boolean;\n\n /**\n * 是否在调用结束后等待一段时间再执行\n * @default false\n */\n trailing?: boolean;\n};\n\n/**\n * 创建一个节流函数,该函数会在指定的等待时间内最多执行一次,如果在等待时间内再次调用,则忽略后续调用。\n *\n * @param fn - 需要节流的函数。\n * @param wait - 等待时间(毫秒)或包含 `wait`、`leading` 和 `trailing` 选项的对象。\n * @returns 返回一个节流函数,该函数具有 `cancel` 方法,用于取消节流操作。\n *\n * @example\n * ```typescript\n * const throttledFn = fnThrottle(() => {\n * console.log('Throttled!');\n * }, 100);\n *\n * throttledFn(); // 立即执行\n * throttledFn(); // 忽略\n * throttledFn.cancel(); // 取消节流操作\n * ```\n */\nexport function fnThrottle<F extends AnyFunction>(fn: F, wait: number | ThrottleOptions) {\n const options = isNumber(wait) ? { wait } : wait;\n const waitFinal = options.wait;\n let lastTime = 0;\n let canceled = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let timer: any;\n\n const throttled = function (this: unknown, ...args: Parameters<F>) {\n if (canceled) return;\n\n const now = Date.now();\n let executed = false;\n\n // 第一次执行\n if (options.leading && lastTime === 0) {\n lastTime = now;\n fn.apply(this, args);\n executed = true;\n }\n\n // 中间控频执行\n else if (lastTime > 0 && now - lastTime >= waitFinal) {\n lastTime = now;\n fn.apply(this, args);\n executed = true;\n }\n\n // 首次计时\n else if (lastTime === 0) {\n lastTime = now;\n }\n\n // 最后一次执行,仅在本次未执行时才设置 trailing\n if (options.trailing && !executed) {\n clearTimeout(timer);\n timer = setTimeout(() => {\n fn.apply(this, args);\n }, waitFinal);\n }\n };\n\n throttled.cancel = () => {\n canceled = true;\n clearTimeout(timer);\n };\n\n return throttled;\n}\n\n/**\n * 创建一个只执行一次的函数,无论调用多少次,实际执行的函数体也只会执行一次。\n *\n * @param fn - 需要只执行一次的函数。\n * @returns 返回一个只执行一次的函数,该函数在第一次调用后会缓存结果并返回缓存的结果。\n *\n * @example\n * ```typescript\n * const onceFn = fnOnce(() => {\n * console.log('This will be logged only once.');\n * return 42;\n * });\n *\n * console.log(onceFn()); // 输出: This will be logged only once. 42\n * console.log(onceFn()); // 输出: 42\n * ```\n */\nexport function fnOnce<F extends AnyFunction>(fn: F) {\n let called = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let result: any;\n\n return function (this: unknown, ...args: Parameters<F>) {\n if (!called) {\n called = true;\n result = fn.apply(this, args);\n }\n\n return result;\n };\n}\n\nexport type FnRetryOptions = {\n /**\n * 最大尝试次数\n */\n maxAttempts?: number;\n /**\n * 每次重试之间的延迟时间(毫秒)\n * @default 0\n */\n delay?: number;\n /**\n * 自定义跳过重试条件,返回 true 时跳过重试(即不重试该错误)\n * @default 所有错误均重试\n */\n skipRetry?: (error: unknown) => boolean;\n};\n\n/**\n * 创建一个带重试能力的函数,失败时自动重试直到成功或耗尽次数。\n *\n * @param fn - 需要重试的函数。\n * @param options - 重试配置选项。\n * @returns 返回一个带重试能力的函数。\n *\n * @example\n * ```typescript\n * const fetchData = fnRetry(async () => {\n * return await api.request('/data');\n * }, { maxAttempts: 3, delay: 1000 });\n *\n * // 第1次失败 → 等待1s → 第2次失败 → 等待1s → 第3次成功 → 返回结果\n * const result = await fetchData();\n * ```\n */\nexport function fnRetry<F extends AnyFunction>(fn: F, options: FnRetryOptions = {}) {\n const { maxAttempts = 3, delay = 0, skipRetry } = options;\n\n return async function (this: unknown, ...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> {\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return (await fn.apply(this, args)) as Awaited<ReturnType<F>>;\n } catch (error) {\n lastError = error;\n\n if (skipRetry && skipRetry(error)) break;\n\n if (attempt < maxAttempts && delay > 0) {\n await promiseDelay(delay);\n }\n }\n }\n\n throw lastError;\n };\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,SAAS,CAEzB;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,WAAkC,IAAO,MAAgC;CACvF,MAAM,UAA2B,aAAA,SAAS,IAAI,IAAI,EAAE,KAAK,IAAI;CAC7D,IAAI,WAAW;CAEf,IAAI;CACJ,IAAI,UAAU;CAEd,MAAM,YAAY,SAAyB,GAAG,MAAqB;EACjE,IAAI,UAAU;EAGd,IAAI,QAAQ,WAAW,CAAC,SAAS;GAC/B,UAAU;GACV,GAAG,MAAM,MAAM,IAAI;GACnB;EACF;EAGA,aAAa,KAAK;EAClB,QAAQ,iBAAiB;GACvB,IAAI,UAAU;GAEd,GAAG,MAAM,MAAM,IAAI;EACrB,GAAG,QAAQ,IAAI;CACjB;CAEA,UAAU,eAAe;EACvB,aAAa,KAAK;EAClB,WAAW;CACb;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,WAAkC,IAAO,MAAgC;CACvF,MAAM,UAAU,aAAA,SAAS,IAAI,IAAI,EAAE,KAAK,IAAI;CAC5C,MAAM,YAAY,QAAQ;CAC1B,IAAI,WAAW;CACf,IAAI,WAAW;CAEf,IAAI;CAEJ,MAAM,YAAY,SAAyB,GAAG,MAAqB;EACjE,IAAI,UAAU;EAEd,MAAM,MAAM,KAAK,IAAI;EACrB,IAAI,WAAW;EAGf,IAAI,QAAQ,WAAW,aAAa,GAAG;GACrC,WAAW;GACX,GAAG,MAAM,MAAM,IAAI;GACnB,WAAW;EACb,OAGK,IAAI,WAAW,KAAK,MAAM,YAAY,WAAW;GACpD,WAAW;GACX,GAAG,MAAM,MAAM,IAAI;GACnB,WAAW;EACb,OAGK,IAAI,aAAa,GACpB,WAAW;EAIb,IAAI,QAAQ,YAAY,CAAC,UAAU;GACjC,aAAa,KAAK;GAClB,QAAQ,iBAAiB;IACvB,GAAG,MAAM,MAAM,IAAI;GACrB,GAAG,SAAS;EACd;CACF;CAEA,UAAU,eAAe;EACvB,WAAW;EACX,aAAa,KAAK;CACpB;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,OAA8B,IAAO;CACnD,IAAI,SAAS;CAEb,IAAI;CAEJ,OAAO,SAAyB,GAAG,MAAqB;EACtD,IAAI,CAAC,QAAQ;GACX,SAAS;GACT,SAAS,GAAG,MAAM,MAAM,IAAI;EAC9B;EAEA,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,QAA+B,IAAO,UAA0B,CAAC,GAAG;CAClF,MAAM,EAAE,cAAc,GAAG,QAAQ,GAAG,cAAc;CAElD,OAAO,eAA+B,GAAG,MAAsD;EAC7F,IAAI;EAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,aAAa,WAC5C,IAAI;GACF,OAAQ,MAAM,GAAG,MAAM,MAAM,IAAI;EACnC,SAAS,OAAO;GACd,YAAY;GAEZ,IAAI,aAAa,UAAU,KAAK,GAAG;GAEnC,IAAI,UAAU,eAAe,QAAQ,GACnC,MAAM,gBAAA,aAAa,KAAK;EAE5B;EAGF,MAAM;CACR;AACF"}
@@ -111,10 +111,10 @@ export type FnRetryOptions = {
111
111
  */
112
112
  delay?: number;
113
113
  /**
114
- * 自定义重试条件,返回 true 时触发重试
115
- * @default 仅在抛出错误时重试
114
+ * 自定义跳过重试条件,返回 true 时跳过重试(即不重试该错误)
115
+ * @default 所有错误均重试
116
116
  */
117
- retryWhen?: (error: unknown) => boolean;
117
+ skipRetry?: (error: unknown) => boolean;
118
118
  };
119
119
  /**
120
120
  * 创建一个带重试能力的函数,失败时自动重试直到成功或耗尽次数。
package/dist/function.mjs CHANGED
@@ -148,14 +148,14 @@ function fnOnce(fn) {
148
148
  * ```
149
149
  */
150
150
  function fnRetry(fn, options = {}) {
151
- const { maxAttempts = 3, delay = 0, retryWhen } = options;
151
+ const { maxAttempts = 3, delay = 0, skipRetry } = options;
152
152
  return async function(...args) {
153
153
  let lastError;
154
154
  for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
155
155
  return await fn.apply(this, args);
156
156
  } catch (error) {
157
157
  lastError = error;
158
- if (retryWhen && !retryWhen(error)) break;
158
+ if (skipRetry && skipRetry(error)) break;
159
159
  if (attempt < maxAttempts && delay > 0) await promiseDelay(delay);
160
160
  }
161
161
  throw lastError;
@@ -1 +1 @@
1
- {"version":3,"file":"function.mjs","names":[],"sources":["../src/function.ts"],"sourcesContent":["import { promiseDelay } from './promise';\nimport { isNumber } from './type';\nimport type { AnyFunction } from './types';\n\n/**\n * 一个空操作函数,不执行任何操作。\n *\n * @example\n * ```typescript\n * fnNoop(); // 不执行任何操作\n * ```\n */\nexport function fnNoop() {\n //\n}\n\n/**\n * 防抖函数的配置选项。\n */\nexport type DebounceOptions = {\n /**\n * 等待时间(毫秒)。\n */\n wait: number;\n /**\n * 是否在等待开始时立即执行一次\n * @default false\n */\n leading?: boolean;\n};\n\n/**\n * 创建一个防抖函数,该函数会在指定的等待时间后执行,如果在等待时间内再次调用,则重新计时。\n *\n * @param fn - 需要防抖的函数。\n * @param wait - 等待时间(毫秒)或包含 `wait` 和 `leading` 选项的对象。\n * @returns 返回一个防抖函数,该函数具有 `cancel` 方法,用于取消防抖操作。\n *\n * @example\n * ```typescript\n * const debouncedFn = fnDebounce(() => {\n * console.log('Debounced!');\n * }, 100);\n *\n * debouncedFn(); // 不会立即执行\n * debouncedFn(); // 重新计时\n * debouncedFn.cancel(); // 取消防抖操作\n * ```\n */\nexport function fnDebounce<F extends AnyFunction>(fn: F, wait: number | DebounceOptions) {\n const options: DebounceOptions = isNumber(wait) ? { wait } : wait;\n let canceled = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let timer: any;\n let leading = false;\n\n const debounced = function (this: unknown, ...args: Parameters<F>) {\n if (canceled) return;\n\n // 第一次执行\n if (options.leading && !leading) {\n leading = true;\n fn.apply(this, args);\n return;\n }\n\n // 最后一次执行\n clearTimeout(timer);\n timer = setTimeout(() => {\n if (canceled) return;\n\n fn.apply(this, args);\n }, options.wait);\n };\n\n debounced.cancel = () => {\n clearTimeout(timer);\n canceled = true;\n };\n\n return debounced;\n}\n\nexport type ThrottleOptions = {\n /**\n * 等待时间(毫秒)。\n */\n wait: number;\n\n /**\n * 是否在第一次调用时立即执行\n * @default false\n */\n leading?: boolean;\n\n /**\n * 是否在调用结束后等待一段时间再执行\n * @default false\n */\n trailing?: boolean;\n};\n\n/**\n * 创建一个节流函数,该函数会在指定的等待时间内最多执行一次,如果在等待时间内再次调用,则忽略后续调用。\n *\n * @param fn - 需要节流的函数。\n * @param wait - 等待时间(毫秒)或包含 `wait`、`leading` 和 `trailing` 选项的对象。\n * @returns 返回一个节流函数,该函数具有 `cancel` 方法,用于取消节流操作。\n *\n * @example\n * ```typescript\n * const throttledFn = fnThrottle(() => {\n * console.log('Throttled!');\n * }, 100);\n *\n * throttledFn(); // 立即执行\n * throttledFn(); // 忽略\n * throttledFn.cancel(); // 取消节流操作\n * ```\n */\nexport function fnThrottle<F extends AnyFunction>(fn: F, wait: number | ThrottleOptions) {\n const options = isNumber(wait) ? { wait } : wait;\n const waitFinal = options.wait;\n let lastTime = 0;\n let canceled = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let timer: any;\n\n const throttled = function (this: unknown, ...args: Parameters<F>) {\n if (canceled) return;\n\n const now = Date.now();\n let executed = false;\n\n // 第一次执行\n if (options.leading && lastTime === 0) {\n lastTime = now;\n fn.apply(this, args);\n executed = true;\n }\n\n // 中间控频执行\n else if (lastTime > 0 && now - lastTime >= waitFinal) {\n lastTime = now;\n fn.apply(this, args);\n executed = true;\n }\n\n // 首次计时\n else if (lastTime === 0) {\n lastTime = now;\n }\n\n // 最后一次执行,仅在本次未执行时才设置 trailing\n if (options.trailing && !executed) {\n clearTimeout(timer);\n timer = setTimeout(() => {\n fn.apply(this, args);\n }, waitFinal);\n }\n };\n\n throttled.cancel = () => {\n canceled = true;\n clearTimeout(timer);\n };\n\n return throttled;\n}\n\n/**\n * 创建一个只执行一次的函数,无论调用多少次,实际执行的函数体也只会执行一次。\n *\n * @param fn - 需要只执行一次的函数。\n * @returns 返回一个只执行一次的函数,该函数在第一次调用后会缓存结果并返回缓存的结果。\n *\n * @example\n * ```typescript\n * const onceFn = fnOnce(() => {\n * console.log('This will be logged only once.');\n * return 42;\n * });\n *\n * console.log(onceFn()); // 输出: This will be logged only once. 42\n * console.log(onceFn()); // 输出: 42\n * ```\n */\nexport function fnOnce<F extends AnyFunction>(fn: F) {\n let called = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let result: any;\n\n return function (this: unknown, ...args: Parameters<F>) {\n if (!called) {\n called = true;\n result = fn.apply(this, args);\n }\n\n return result;\n };\n}\n\nexport type FnRetryOptions = {\n /**\n * 最大尝试次数\n */\n maxAttempts?: number;\n /**\n * 每次重试之间的延迟时间(毫秒)\n * @default 0\n */\n delay?: number;\n /**\n * 自定义重试条件,返回 true 时触发重试\n * @default 仅在抛出错误时重试\n */\n retryWhen?: (error: unknown) => boolean;\n};\n\n/**\n * 创建一个带重试能力的函数,失败时自动重试直到成功或耗尽次数。\n *\n * @param fn - 需要重试的函数。\n * @param options - 重试配置选项。\n * @returns 返回一个带重试能力的函数。\n *\n * @example\n * ```typescript\n * const fetchData = fnRetry(async () => {\n * return await api.request('/data');\n * }, { maxAttempts: 3, delay: 1000 });\n *\n * // 第1次失败 → 等待1s → 第2次失败 → 等待1s → 第3次成功 → 返回结果\n * const result = await fetchData();\n * ```\n */\nexport function fnRetry<F extends AnyFunction>(fn: F, options: FnRetryOptions = {}) {\n const { maxAttempts = 3, delay = 0, retryWhen } = options;\n\n return async function (this: unknown, ...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> {\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return (await fn.apply(this, args)) as Awaited<ReturnType<F>>;\n } catch (error) {\n lastError = error;\n\n if (retryWhen && !retryWhen(error)) break;\n\n if (attempt < maxAttempts && delay > 0) {\n await promiseDelay(delay);\n }\n }\n }\n\n throw lastError;\n };\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,SAAS,CAEzB;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,WAAkC,IAAO,MAAgC;CACvF,MAAM,UAA2B,SAAS,IAAI,IAAI,EAAE,KAAK,IAAI;CAC7D,IAAI,WAAW;CAEf,IAAI;CACJ,IAAI,UAAU;CAEd,MAAM,YAAY,SAAyB,GAAG,MAAqB;EACjE,IAAI,UAAU;EAGd,IAAI,QAAQ,WAAW,CAAC,SAAS;GAC/B,UAAU;GACV,GAAG,MAAM,MAAM,IAAI;GACnB;EACF;EAGA,aAAa,KAAK;EAClB,QAAQ,iBAAiB;GACvB,IAAI,UAAU;GAEd,GAAG,MAAM,MAAM,IAAI;EACrB,GAAG,QAAQ,IAAI;CACjB;CAEA,UAAU,eAAe;EACvB,aAAa,KAAK;EAClB,WAAW;CACb;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,WAAkC,IAAO,MAAgC;CACvF,MAAM,UAAU,SAAS,IAAI,IAAI,EAAE,KAAK,IAAI;CAC5C,MAAM,YAAY,QAAQ;CAC1B,IAAI,WAAW;CACf,IAAI,WAAW;CAEf,IAAI;CAEJ,MAAM,YAAY,SAAyB,GAAG,MAAqB;EACjE,IAAI,UAAU;EAEd,MAAM,MAAM,KAAK,IAAI;EACrB,IAAI,WAAW;EAGf,IAAI,QAAQ,WAAW,aAAa,GAAG;GACrC,WAAW;GACX,GAAG,MAAM,MAAM,IAAI;GACnB,WAAW;EACb,OAGK,IAAI,WAAW,KAAK,MAAM,YAAY,WAAW;GACpD,WAAW;GACX,GAAG,MAAM,MAAM,IAAI;GACnB,WAAW;EACb,OAGK,IAAI,aAAa,GACpB,WAAW;EAIb,IAAI,QAAQ,YAAY,CAAC,UAAU;GACjC,aAAa,KAAK;GAClB,QAAQ,iBAAiB;IACvB,GAAG,MAAM,MAAM,IAAI;GACrB,GAAG,SAAS;EACd;CACF;CAEA,UAAU,eAAe;EACvB,WAAW;EACX,aAAa,KAAK;CACpB;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,OAA8B,IAAO;CACnD,IAAI,SAAS;CAEb,IAAI;CAEJ,OAAO,SAAyB,GAAG,MAAqB;EACtD,IAAI,CAAC,QAAQ;GACX,SAAS;GACT,SAAS,GAAG,MAAM,MAAM,IAAI;EAC9B;EAEA,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,QAA+B,IAAO,UAA0B,CAAC,GAAG;CAClF,MAAM,EAAE,cAAc,GAAG,QAAQ,GAAG,cAAc;CAElD,OAAO,eAA+B,GAAG,MAAsD;EAC7F,IAAI;EAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,aAAa,WAC5C,IAAI;GACF,OAAQ,MAAM,GAAG,MAAM,MAAM,IAAI;EACnC,SAAS,OAAO;GACd,YAAY;GAEZ,IAAI,aAAa,CAAC,UAAU,KAAK,GAAG;GAEpC,IAAI,UAAU,eAAe,QAAQ,GACnC,MAAM,aAAa,KAAK;EAE5B;EAGF,MAAM;CACR;AACF"}
1
+ {"version":3,"file":"function.mjs","names":[],"sources":["../src/function.ts"],"sourcesContent":["import { promiseDelay } from './promise';\nimport { isNumber } from './type';\nimport type { AnyFunction } from './types';\n\n/**\n * 一个空操作函数,不执行任何操作。\n *\n * @example\n * ```typescript\n * fnNoop(); // 不执行任何操作\n * ```\n */\nexport function fnNoop() {\n //\n}\n\n/**\n * 防抖函数的配置选项。\n */\nexport type DebounceOptions = {\n /**\n * 等待时间(毫秒)。\n */\n wait: number;\n /**\n * 是否在等待开始时立即执行一次\n * @default false\n */\n leading?: boolean;\n};\n\n/**\n * 创建一个防抖函数,该函数会在指定的等待时间后执行,如果在等待时间内再次调用,则重新计时。\n *\n * @param fn - 需要防抖的函数。\n * @param wait - 等待时间(毫秒)或包含 `wait` 和 `leading` 选项的对象。\n * @returns 返回一个防抖函数,该函数具有 `cancel` 方法,用于取消防抖操作。\n *\n * @example\n * ```typescript\n * const debouncedFn = fnDebounce(() => {\n * console.log('Debounced!');\n * }, 100);\n *\n * debouncedFn(); // 不会立即执行\n * debouncedFn(); // 重新计时\n * debouncedFn.cancel(); // 取消防抖操作\n * ```\n */\nexport function fnDebounce<F extends AnyFunction>(fn: F, wait: number | DebounceOptions) {\n const options: DebounceOptions = isNumber(wait) ? { wait } : wait;\n let canceled = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let timer: any;\n let leading = false;\n\n const debounced = function (this: unknown, ...args: Parameters<F>) {\n if (canceled) return;\n\n // 第一次执行\n if (options.leading && !leading) {\n leading = true;\n fn.apply(this, args);\n return;\n }\n\n // 最后一次执行\n clearTimeout(timer);\n timer = setTimeout(() => {\n if (canceled) return;\n\n fn.apply(this, args);\n }, options.wait);\n };\n\n debounced.cancel = () => {\n clearTimeout(timer);\n canceled = true;\n };\n\n return debounced;\n}\n\nexport type ThrottleOptions = {\n /**\n * 等待时间(毫秒)。\n */\n wait: number;\n\n /**\n * 是否在第一次调用时立即执行\n * @default false\n */\n leading?: boolean;\n\n /**\n * 是否在调用结束后等待一段时间再执行\n * @default false\n */\n trailing?: boolean;\n};\n\n/**\n * 创建一个节流函数,该函数会在指定的等待时间内最多执行一次,如果在等待时间内再次调用,则忽略后续调用。\n *\n * @param fn - 需要节流的函数。\n * @param wait - 等待时间(毫秒)或包含 `wait`、`leading` 和 `trailing` 选项的对象。\n * @returns 返回一个节流函数,该函数具有 `cancel` 方法,用于取消节流操作。\n *\n * @example\n * ```typescript\n * const throttledFn = fnThrottle(() => {\n * console.log('Throttled!');\n * }, 100);\n *\n * throttledFn(); // 立即执行\n * throttledFn(); // 忽略\n * throttledFn.cancel(); // 取消节流操作\n * ```\n */\nexport function fnThrottle<F extends AnyFunction>(fn: F, wait: number | ThrottleOptions) {\n const options = isNumber(wait) ? { wait } : wait;\n const waitFinal = options.wait;\n let lastTime = 0;\n let canceled = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let timer: any;\n\n const throttled = function (this: unknown, ...args: Parameters<F>) {\n if (canceled) return;\n\n const now = Date.now();\n let executed = false;\n\n // 第一次执行\n if (options.leading && lastTime === 0) {\n lastTime = now;\n fn.apply(this, args);\n executed = true;\n }\n\n // 中间控频执行\n else if (lastTime > 0 && now - lastTime >= waitFinal) {\n lastTime = now;\n fn.apply(this, args);\n executed = true;\n }\n\n // 首次计时\n else if (lastTime === 0) {\n lastTime = now;\n }\n\n // 最后一次执行,仅在本次未执行时才设置 trailing\n if (options.trailing && !executed) {\n clearTimeout(timer);\n timer = setTimeout(() => {\n fn.apply(this, args);\n }, waitFinal);\n }\n };\n\n throttled.cancel = () => {\n canceled = true;\n clearTimeout(timer);\n };\n\n return throttled;\n}\n\n/**\n * 创建一个只执行一次的函数,无论调用多少次,实际执行的函数体也只会执行一次。\n *\n * @param fn - 需要只执行一次的函数。\n * @returns 返回一个只执行一次的函数,该函数在第一次调用后会缓存结果并返回缓存的结果。\n *\n * @example\n * ```typescript\n * const onceFn = fnOnce(() => {\n * console.log('This will be logged only once.');\n * return 42;\n * });\n *\n * console.log(onceFn()); // 输出: This will be logged only once. 42\n * console.log(onceFn()); // 输出: 42\n * ```\n */\nexport function fnOnce<F extends AnyFunction>(fn: F) {\n let called = false;\n // biome-ignore lint/suspicious/noExplicitAny: 内部使用\n let result: any;\n\n return function (this: unknown, ...args: Parameters<F>) {\n if (!called) {\n called = true;\n result = fn.apply(this, args);\n }\n\n return result;\n };\n}\n\nexport type FnRetryOptions = {\n /**\n * 最大尝试次数\n */\n maxAttempts?: number;\n /**\n * 每次重试之间的延迟时间(毫秒)\n * @default 0\n */\n delay?: number;\n /**\n * 自定义跳过重试条件,返回 true 时跳过重试(即不重试该错误)\n * @default 所有错误均重试\n */\n skipRetry?: (error: unknown) => boolean;\n};\n\n/**\n * 创建一个带重试能力的函数,失败时自动重试直到成功或耗尽次数。\n *\n * @param fn - 需要重试的函数。\n * @param options - 重试配置选项。\n * @returns 返回一个带重试能力的函数。\n *\n * @example\n * ```typescript\n * const fetchData = fnRetry(async () => {\n * return await api.request('/data');\n * }, { maxAttempts: 3, delay: 1000 });\n *\n * // 第1次失败 → 等待1s → 第2次失败 → 等待1s → 第3次成功 → 返回结果\n * const result = await fetchData();\n * ```\n */\nexport function fnRetry<F extends AnyFunction>(fn: F, options: FnRetryOptions = {}) {\n const { maxAttempts = 3, delay = 0, skipRetry } = options;\n\n return async function (this: unknown, ...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> {\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return (await fn.apply(this, args)) as Awaited<ReturnType<F>>;\n } catch (error) {\n lastError = error;\n\n if (skipRetry && skipRetry(error)) break;\n\n if (attempt < maxAttempts && delay > 0) {\n await promiseDelay(delay);\n }\n }\n }\n\n throw lastError;\n };\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,SAAS,CAEzB;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,WAAkC,IAAO,MAAgC;CACvF,MAAM,UAA2B,SAAS,IAAI,IAAI,EAAE,KAAK,IAAI;CAC7D,IAAI,WAAW;CAEf,IAAI;CACJ,IAAI,UAAU;CAEd,MAAM,YAAY,SAAyB,GAAG,MAAqB;EACjE,IAAI,UAAU;EAGd,IAAI,QAAQ,WAAW,CAAC,SAAS;GAC/B,UAAU;GACV,GAAG,MAAM,MAAM,IAAI;GACnB;EACF;EAGA,aAAa,KAAK;EAClB,QAAQ,iBAAiB;GACvB,IAAI,UAAU;GAEd,GAAG,MAAM,MAAM,IAAI;EACrB,GAAG,QAAQ,IAAI;CACjB;CAEA,UAAU,eAAe;EACvB,aAAa,KAAK;EAClB,WAAW;CACb;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,WAAkC,IAAO,MAAgC;CACvF,MAAM,UAAU,SAAS,IAAI,IAAI,EAAE,KAAK,IAAI;CAC5C,MAAM,YAAY,QAAQ;CAC1B,IAAI,WAAW;CACf,IAAI,WAAW;CAEf,IAAI;CAEJ,MAAM,YAAY,SAAyB,GAAG,MAAqB;EACjE,IAAI,UAAU;EAEd,MAAM,MAAM,KAAK,IAAI;EACrB,IAAI,WAAW;EAGf,IAAI,QAAQ,WAAW,aAAa,GAAG;GACrC,WAAW;GACX,GAAG,MAAM,MAAM,IAAI;GACnB,WAAW;EACb,OAGK,IAAI,WAAW,KAAK,MAAM,YAAY,WAAW;GACpD,WAAW;GACX,GAAG,MAAM,MAAM,IAAI;GACnB,WAAW;EACb,OAGK,IAAI,aAAa,GACpB,WAAW;EAIb,IAAI,QAAQ,YAAY,CAAC,UAAU;GACjC,aAAa,KAAK;GAClB,QAAQ,iBAAiB;IACvB,GAAG,MAAM,MAAM,IAAI;GACrB,GAAG,SAAS;EACd;CACF;CAEA,UAAU,eAAe;EACvB,WAAW;EACX,aAAa,KAAK;CACpB;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,OAA8B,IAAO;CACnD,IAAI,SAAS;CAEb,IAAI;CAEJ,OAAO,SAAyB,GAAG,MAAqB;EACtD,IAAI,CAAC,QAAQ;GACX,SAAS;GACT,SAAS,GAAG,MAAM,MAAM,IAAI;EAC9B;EAEA,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,QAA+B,IAAO,UAA0B,CAAC,GAAG;CAClF,MAAM,EAAE,cAAc,GAAG,QAAQ,GAAG,cAAc;CAElD,OAAO,eAA+B,GAAG,MAAsD;EAC7F,IAAI;EAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,aAAa,WAC5C,IAAI;GACF,OAAQ,MAAM,GAAG,MAAM,MAAM,IAAI;EACnC,SAAS,OAAO;GACd,YAAY;GAEZ,IAAI,aAAa,UAAU,KAAK,GAAG;GAEnC,IAAI,UAAU,eAAe,QAAQ,GACnC,MAAM,aAAa,KAAK;EAE5B;EAGF,MAAM;CACR;AACF"}
package/dist/index.cjs CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  /**
4
4
  * `@cloudcome/utils-core` 版本号
5
5
  */
6
- var VERSION = "1.23.1";
6
+ var VERSION = "1.24.1";
7
7
  //#endregion
8
8
  exports.VERSION = VERSION;
9
9
 
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * `@cloudcome/utils-core` 版本号
4
4
  */
5
- var VERSION = "1.23.1";
5
+ var VERSION = "1.24.1";
6
6
  //#endregion
7
7
  export { VERSION };
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudcome/utils-core",
3
- "version": "1.23.1",
3
+ "version": "1.24.1",
4
4
  "description": "cloudcome core utils",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",