foreslash 0.2.2 → 0.2.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Change Log
2
2
 
3
+ ## Version 0.2.4 - 2025-0
4
+
5
+ Unstable version
6
+
7
+ - Feat 🥥 Functions added: `retry` `parallel`
8
+ - Other fixes and improvements
9
+
10
+ 不稳定版本
11
+
12
+ - 功能 🥥 添加函数: `retry` `parallel`
13
+ - 其他修复与优化
14
+
15
+ ## Version 0.2.3 - 2025-03-28
16
+
17
+ Unstable version
18
+
19
+ - Feat 🥥 Functions added: `defer`
20
+ - Fix 🥕 Bug fixed: `debounce` doesn't apply the last callee's arguments
21
+ - Other fixes and improvements
22
+
23
+ 不稳定版本
24
+
25
+ - 功能 🥥 添加函数: `defer`
26
+ - 修复 🥕 缺陷修复: `debounce` 没有传入最后一次获取的参数
27
+ - 其他修复与优化
28
+
3
29
  ## Version 0.2.2 - 2025-03-18
4
30
 
5
31
  Unstable version
package/lib/index.cmn.cjs CHANGED
@@ -67,11 +67,37 @@ function remove(arr, ...item) {
67
67
  return res;
68
68
  }
69
69
 
70
- function sleep(time = 1000) {
71
- return new Promise((res) => {
72
- setTimeout(res, time);
73
- });
74
- }
70
+ /******************************************************************************
71
+ Copyright (c) Microsoft Corporation.
72
+
73
+ Permission to use, copy, modify, and/or distribute this software for any
74
+ purpose with or without fee is hereby granted.
75
+
76
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
77
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
78
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
79
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
80
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
81
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
82
+ PERFORMANCE OF THIS SOFTWARE.
83
+ ***************************************************************************** */
84
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
85
+
86
+
87
+ function __awaiter(thisArg, _arguments, P, generator) {
88
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
89
+ return new (P || (P = Promise))(function (resolve, reject) {
90
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
91
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
92
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
93
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
94
+ });
95
+ }
96
+
97
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
98
+ var e = new Error(message);
99
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
100
+ };
75
101
 
76
102
  const isArray = Array.isArray;
77
103
 
@@ -171,7 +197,7 @@ function isNumber(value) {
171
197
  }
172
198
 
173
199
  function isPrimitive(value) {
174
- return value == null || (typeof value !== 'object' && typeof value !== 'function');
200
+ return value === undefined || value === null || (typeof value !== 'object' && typeof value !== 'function');
175
201
  }
176
202
 
177
203
  function isPromise(value) {
@@ -292,17 +318,37 @@ function tryit(fn) {
292
318
  };
293
319
  }
294
320
 
295
- const noop = function noop() { };
296
-
297
- function withResolvers(PromiseLike = Promise) {
298
- let promise;
299
- let resolve = noop;
300
- let reject = noop;
301
- promise = new PromiseLike((res, rej) => {
302
- resolve = res;
303
- reject = rej;
321
+ function defer(asyncFunction, options) {
322
+ return __awaiter(this, void 0, void 0, function* () {
323
+ const queue = [];
324
+ const { rethrow = false } = options || {};
325
+ const defaultOption = { rethrow };
326
+ const cleanUp = (fn, options) => {
327
+ queue.push({ fn, opt: Object.assign(defaultOption, options) });
328
+ return queue.length - 1;
329
+ };
330
+ const cancelCleanUp = (fnOrIndex) => {
331
+ if (isInteger(fnOrIndex) && fnOrIndex > -1)
332
+ queue[fnOrIndex] = null;
333
+ else if (isFunction(fnOrIndex)) {
334
+ const i = queue.findIndex((item) => item && item.fn === fnOrIndex);
335
+ if (i > -1)
336
+ queue[i] = null;
337
+ }
338
+ };
339
+ const [err, res] = yield tryit(asyncFunction)(cleanUp, cancelCleanUp);
340
+ for (const item of queue) {
341
+ if (!item)
342
+ continue;
343
+ const { fn, opt } = item;
344
+ const [cleanUpErr] = yield tryit(fn)(err);
345
+ if (cleanUpErr && opt.rethrow)
346
+ throw cleanUpErr;
347
+ }
348
+ if (err)
349
+ throw err;
350
+ return res;
304
351
  });
305
- return { promise, resolve, reject };
306
352
  }
307
353
 
308
354
  function clamp(num, min, max, options) {
@@ -322,6 +368,96 @@ function clamp(num, min, max, options) {
322
368
  return num < min ? defaultMin : num > max ? defaultMax : num;
323
369
  }
324
370
 
371
+ function parallel(args, fn, options) {
372
+ return __awaiter(this, void 0, void 0, function* () {
373
+ if (!args.length)
374
+ return [];
375
+ const { limit: _limit = 5 } = options || {};
376
+ const limit = clamp(Math.floor(_limit), 1, 100);
377
+ let current = 0;
378
+ const results = [];
379
+ const errors = [];
380
+ const asyncFn = tryit(fn);
381
+ const processor = () => __awaiter(this, void 0, void 0, function* () {
382
+ while (current < args.length) {
383
+ const index = current++;
384
+ const [err, result] = yield asyncFn(args[index]);
385
+ if (err)
386
+ errors.push({ index, error: err });
387
+ else
388
+ results[index] = result;
389
+ }
390
+ });
391
+ const tasks = [];
392
+ for (let i = 0; i < Math.min(args.length, limit); i++) {
393
+ tasks.push(processor());
394
+ }
395
+ yield Promise.all(tasks);
396
+ if (errors.length) {
397
+ throw new Error(`Parallel execution failed on index: ${errors.map((e) => e.index).join(', ')}`, { cause: errors });
398
+ }
399
+ return results;
400
+ });
401
+ }
402
+
403
+ function sleep(time = 1000) {
404
+ return new Promise((res) => {
405
+ setTimeout(res, time);
406
+ });
407
+ }
408
+
409
+ function retry(asyncFunction, option) {
410
+ return __awaiter(this, void 0, void 0, function* () {
411
+ let retryCounts = 0;
412
+ const times = isNumber(option === null || option === void 0 ? void 0 : option.times) ? option.times : 3;
413
+ const delay = isFunction(option === null || option === void 0 ? void 0 : option.delay)
414
+ ? option.delay
415
+ : isNumber(option === null || option === void 0 ? void 0 : option.delay)
416
+ ? () => option.delay
417
+ : null;
418
+ const gap = isFunction(option === null || option === void 0 ? void 0 : option.gap) ? option.gap : isNumber(option === null || option === void 0 ? void 0 : option.gap) ? () => option.gap : null;
419
+ let lastRunTime = 0;
420
+ const getDelayTime = !option || (!delay && !gap)
421
+ ? () => 0
422
+ : gap
423
+ ? (retryCounts) => {
424
+ const time = gap(retryCounts);
425
+ return time - Date.now() + lastRunTime;
426
+ }
427
+ : delay;
428
+ while (1) {
429
+ lastRunTime = Date.now();
430
+ const [err, res] = yield tryit(asyncFunction)((err) => {
431
+ throw { $$exit_retry: err };
432
+ });
433
+ if (!err)
434
+ return res;
435
+ retryCounts++;
436
+ if (err && err.$$exit_retry)
437
+ throw err.$$exit_retry;
438
+ if (retryCounts >= times)
439
+ throw err;
440
+ const delayTime = getDelayTime(retryCounts);
441
+ if (delayTime > 0)
442
+ yield sleep(delayTime);
443
+ }
444
+ throw new Error('retry failed');
445
+ });
446
+ }
447
+
448
+ const noop = function noop() { };
449
+
450
+ function withResolvers(PromiseLike = Promise) {
451
+ let promise;
452
+ let resolve = noop;
453
+ let reject = noop;
454
+ promise = new PromiseLike((res, rej) => {
455
+ resolve = res;
456
+ reject = rej;
457
+ });
458
+ return { promise, resolve, reject };
459
+ }
460
+
325
461
  const mimeMap = {
326
462
  application: {
327
463
  acrobat: ['pdf'],
@@ -1321,9 +1457,10 @@ function _curryMore(fn) {
1321
1457
  }
1322
1458
 
1323
1459
  function _throttle(fn, delay, options) {
1324
- var _a, _b;
1460
+ var _a, _b, _c;
1325
1461
  const trailing = (_a = options === null || options === void 0 ? void 0 : options.trailing) !== null && _a !== void 0 ? _a : false;
1326
- const leading = (_b = options === null || options === void 0 ? void 0 : options.leading) !== null && _b !== void 0 ? _b : true;
1462
+ const trailingRunLast = (_b = options === null || options === void 0 ? void 0 : options.trailingRunLast) !== null && _b !== void 0 ? _b : true;
1463
+ const leading = (_c = options === null || options === void 0 ? void 0 : options.leading) !== null && _c !== void 0 ? _c : true;
1327
1464
  let timer = null;
1328
1465
  let lastTime = 0;
1329
1466
  const clearTimer = () => {
@@ -1343,6 +1480,9 @@ function _throttle(fn, delay, options) {
1343
1480
  lastTime = now;
1344
1481
  clearTimer();
1345
1482
  }
1483
+ if (timeGap < 0 && trailing && trailingRunLast && timer) {
1484
+ clearTimer();
1485
+ }
1346
1486
  if (timeGap >= 0 && leading) {
1347
1487
  fn.apply(this, args);
1348
1488
  }
@@ -1360,7 +1500,7 @@ function debounce(fn, delay, options) {
1360
1500
  if (!isNumber(delay) || !isFinite(delay) || delay <= 0) {
1361
1501
  throw new Error('Invalid delay parameter');
1362
1502
  }
1363
- return _throttle(fn, delay, Object.assign({ trailing: true, leading: false }, options));
1503
+ return _throttle(fn, delay, Object.assign({ trailing: true, leading: false, trailingRunLast: true }, options));
1364
1504
  }
1365
1505
 
1366
1506
  function _cloneArray(obj, map, cloner, ...args) {
@@ -1648,6 +1788,7 @@ exports.compose = compose;
1648
1788
  exports.curry = _curryMore;
1649
1789
  exports.debounce = debounce;
1650
1790
  exports.deepClone = deepClone;
1791
+ exports.defer = defer;
1651
1792
  exports.fastClone = fastClone;
1652
1793
  exports.getAcceptableExtByMIME = getAcceptableExtByMIME;
1653
1794
  exports.getAcceptableMIMEByExt = getAcceptableMIMEByExt;
@@ -1705,6 +1846,7 @@ exports.kebabCase = kebabCase;
1705
1846
  exports.memo = memo;
1706
1847
  exports.noop = noop;
1707
1848
  exports.not = not;
1849
+ exports.parallel = parallel;
1708
1850
  exports.pascalCase = pascalCase;
1709
1851
  exports.pass = pass;
1710
1852
  exports.passWith = passWith;
@@ -1717,6 +1859,7 @@ exports.randomIntFloor = randomIntFloor;
1717
1859
  exports.randomString = randomString;
1718
1860
  exports.range = range;
1719
1861
  exports.remove = remove;
1862
+ exports.retry = retry;
1720
1863
  exports.shuffle = shuffle;
1721
1864
  exports.sleep = sleep;
1722
1865
  exports.snakeCase = snakeCase;
package/lib/index.d.ts CHANGED
@@ -77,6 +77,109 @@ declare function range<T = number>(start: number, end: number, options?: RangeOp
77
77
  */
78
78
  declare function remove<T>(arr: ArrayLike<T>, ...item: (T | ((val: T) => unknown))[]): T[];
79
79
 
80
+ type DeferCallbackFunction = (error?: Error) => unknown;
81
+ type DeferOption = {
82
+ rethrow: boolean;
83
+ };
84
+ /**
85
+ * 在传入的函数执行完毕后(或是抛出异常后), 执行*延迟操作*\
86
+ * 使用场景一般是处理“副作用”, 类似 `try {} finally {}` 结构但是更加灵活
87
+ * - 执行*延迟操作*的时机是函数执行完毕后(或是抛出异常后), 同时 `defer` 返回的 `Promise` 并未结束
88
+ * - 这个设计是防止*延迟操作*与后续逻辑产生干扰
89
+ * - `defer` 返回的 `Promise` 会根据传入的函数是否正常处理/抛出异常而变化
90
+ * - 传入的函数正常处理, 结果为 `1`, 那么 `defer` 将返回 `Promise {<fulfilled>: 1}`
91
+ * - 传入的函数抛出异常, 内容为 `2`, 那么 `defer` 将返回 `Promise {<rejected>: 2}`
92
+ *
93
+ * *延迟操作*时抛出的错误不会正常抛出
94
+ * - 如果希望执行*延迟操作*时正常抛错, 您可以配置 `options.rethrow = true`
95
+ * @param asyncFunction 需要在执行完毕后做额外处理函数
96
+ * @param options.rethrow 执行*延迟操作*时是否正常抛错, 默认不抛错
97
+ * @returns 一个 `Promise` 会根据传入的函数是否正常处理/抛出异常而变化
98
+ * @example
99
+ * ```js
100
+ * // 一般用途
101
+ * defer((cleanUp) => {
102
+ * cleanUp(() => console.log(2))
103
+ * cleanUp(() => console.log(3))
104
+ * console.log(1)
105
+ * }) // 依次输出 1 2 3
106
+ * // 可以动态取消计划好的延迟操作
107
+ * defer((cleanUp, cancelCleanUp) => {
108
+ * const cleanUpId = cleanUp(() => console.log(123)) // 会返回一个数字作为 id
109
+ * cleanUp(() => console.log(5))
110
+ * cleanUp(() => console.log(6))
111
+ * console.log(4)
112
+ * cancelCleanUp(cleanUpId) // 可以用于取消指定的延迟操作
113
+ * }) // 依次输出 4 5 6
114
+ * ```
115
+ * @version 0.2.3
116
+ */
117
+ declare function defer<T>(asyncFunction: (cleanUp: (fn: DeferCallbackFunction) => number, cancelCleanUp: (fnOrIndex: DeferCallbackFunction | number) => void) => T | Promise<T>, options?: Partial<DeferOption>): Promise<T>;
118
+
119
+ /**
120
+ * 并发执行函数, 可以控制并发数
121
+ * @param args 要经由 `fn` 处理的数据
122
+ * @param fn 处理函数, 可以是异步函数
123
+ * @param options 配置项, 目前仅支持 `limit` 限制并发数, 默认为 `5`
124
+ * @returns 一个 `Promise`, 当所有数据都处理完成后, 会返回一个包含所有结果的数组\
125
+ * 如果有错误, 则会抛出一个 `Error` 对象, `Error.cause` 中包含所有错误信息(如果环境支持的话)
126
+ * @example
127
+ * ```js
128
+ * const fn = async (n) => { return n * 2 }
129
+ * parallel([1, 2, 3, 4, 5], fn, { limit: 2 }) // Promise<[2, 4, 6, 8, 10]>
130
+ * ```
131
+ * @since 0.2.4
132
+ */
133
+ declare function parallel<Args, Res>(args: Args[], fn: (arg: Args) => Promise<Res>, options?: {
134
+ limit?: number;
135
+ }): Promise<Res[]>;
136
+
137
+ type RetryFunction<T> = (() => T | Promise<T>) | ((exitCallback: (err: any) => never) => T | Promise<T>);
138
+ type RetryOption = {
139
+ times?: number;
140
+ delay?: number | ((retryCounts: number) => number);
141
+ gap?: number | ((retryCounts: number) => number);
142
+ };
143
+ /**
144
+ * 在传入的函数发生异常后重试\
145
+ * 使用场景一般是重试网络请求等情况
146
+ * - 可以设置两次重试之间的延迟(delay), 默认为无延迟
147
+ * - 也可以设置为两次重试之间的时间间隔(gap), 与延迟模式的区别见下文
148
+ * - 传入的函数接受一个 `exit` 回调函数, 调用这个回调函数会立即退出重试逻辑
149
+ *
150
+ * 延迟模式(delay)
151
+ * ``` text
152
+ * delay = 1000:
153
+ * |0s |1s |2s |3s
154
+ * |[400ms-] | [800ms------|---] | [第 3 次调用]
155
+ * ^ 1000ms ++++|+++++++ ^ ^ 1000ms ++++++++|+++ ^
156
+ * ```
157
+ *
158
+ * 间隔模式(gap)
159
+ * ``` text
160
+ * gap = 1000:
161
+ * |0s |1s |2s
162
+ * |[400ms-] |[800ms---------] |[第 3 次调用]
163
+ * ^ 1000ms +++++++++++ ^ 1000ms +++++++++++ ^
164
+ * ```
165
+ *
166
+ * @param asyncFunction 需要处理的函数, 推荐是异步函数, 也可以是同步函数
167
+ * @param option 重试相关的配置, 具体配置见下
168
+ * @example
169
+ * ```js
170
+ * // 基本用法, 默认重试 3 次, 每次失败后会立即重新调用
171
+ * const res = await retry(() => fetch(url, params))
172
+ * // 重试 10 次
173
+ * const res = await retry(() => fetch(url, params), { times: 10 })
174
+ * // 延迟模式, 每次失败后会等待 1 秒再重新调用
175
+ * const res = await retry(() => fetch(url, params), { delay: 1000 })
176
+ * // 间隔模式, 每次失败后会在下 1 秒再重新调用
177
+ * const res = await retry(() => fetch(url, params), { gap: 1000 })
178
+ * ```
179
+ * @version 0.2.4
180
+ */
181
+ declare function retry<T>(asyncFunction: RetryFunction<T>, option?: RetryOption): Promise<T>;
182
+
80
183
  /**
81
184
  * 延迟一定时间
82
185
  * @param time 延迟时间, 单位为毫秒(ms), 默认为 `1000` (即 1 秒)
@@ -729,7 +832,7 @@ declare function isWrapperBigInt(value: unknown): value is BigInt;
729
832
  * @param options 配置项, 可以配置默认值等
730
833
  * - `default` 默认值, 如果初始值不在范围内, 则返回默认值
731
834
  * - `defaultMin` 初始值小于最小值时返回该值, 覆盖 `default` 参数
732
- * - `defaultMax` 初始值大于最大值时返回该值
835
+ * - `defaultMax` 初始值大于最大值时返回该值, 覆盖 `default` 参数
733
836
  * @returns 返回一个在指定范围内的数字
734
837
  * @example
735
838
  * ```js
@@ -740,6 +843,7 @@ declare function isWrapperBigInt(value: unknown): value is BigInt;
740
843
  * clamp(15, 0, 10, { default: 6 }) // 6
741
844
  * clamp(-5, 0, 10, { default: 6 }) // 6
742
845
  * ```
846
+ * @version 0.2.2
743
847
  */
744
848
  declare function clamp(num: number, min: number, max: number, options?: {
745
849
  default?: number;
@@ -1320,6 +1424,8 @@ declare function _curryMore<Args extends Array<any>, Res>(fn: (...args: Args) =>
1320
1424
  interface ThrottleOptions {
1321
1425
  /** 节流时触发的最后一次调用是否执行, 默认为否 */
1322
1426
  trailing?: boolean;
1427
+ /** 触发的最后一次调用是否使用最后一次的参数, 默认为是 */
1428
+ trailingRunLast?: boolean;
1323
1429
  /** 第一次触发是否直接执行, 默认为是 */
1324
1430
  leading?: boolean;
1325
1431
  }
@@ -1386,7 +1492,7 @@ declare function deepClone<T>(obj: T, options?: Partial<CloneOptions>, map?: Map
1386
1492
 
1387
1493
  /**
1388
1494
  * 快速深拷贝
1389
- * - 相对 `deepClone` 而言运行更快, 功能较为齐全
1495
+ * - 功能较为齐全, 相对 `deepClone` 而言运行更快
1390
1496
  * - 支持处理的情况:循环引用、数组、`Date`、正则、`Set`、`Map`、`FormData`
1391
1497
  * - 对象上以 `Symbol` 为键的属性无法拷贝
1392
1498
  * - 无法拷贝的内容将视为原生数据类型, 直接复制(如函数、`Promise`、`WeakMap`、`WeakSet`)
@@ -1631,4 +1737,4 @@ declare function throttle<T extends any[]>(fn: (...args: T) => any, delay: numbe
1631
1737
  reset: () => void;
1632
1738
  };
1633
1739
 
1634
- export { type CloneOptions, type CustomCloner, type RangeOptions, type TypedArray, _, acceptableFileName, acceptableFileType, camelCase, caseCamel, caseConvert, caseKebab, casePascal, caseSnake, clamp, compose, _curryMore as curry, debounce, deepClone, fastClone, getAcceptableExtByMIME, getAcceptableMIMEByExt, getGlobalThis, getTag, isArray, isArrayBuffer, isArrayLike, isBigInt, isBigInt64Array, isBigUint64Array, isBoolean, isBuffer, isDataView, isDate, isEmpty, isFile, isFloat32Array, isFloat64Array, isFormData, isFunction, isInt16Array, isInt32Array, isInt8Array, isInteger, isIterable, isMap, isNil, isNull, isNumber, isObject, isPlaceholder, isPrimitive, isPromise, isPromiseLike, isRegExp, isSet, isString, isSymbol, isTypedArray, isUint16Array, isUint32Array, isUint8Array, isUint8ClampedArray, isUndefined, isWeakMap, isWeakSet, isWrapperBigInt, isWrapperBoolean, isWrapperNumber, isWrapperObject, isWrapperString, isWrapperSymbol, kebabCase, memo, noop, not, pascalCase, pass, passWith, pipe, randomBase32String, randomChoice, randomHexString, randomInt, randomIntFloor, randomString, range, remove, shuffle, sleep, snakeCase, splitWords, throttle, titleCase, tryit, ulid, uuidNil, uuidV4, withResolvers };
1740
+ export { type CloneOptions, type CustomCloner, type RangeOptions, type TypedArray, _, acceptableFileName, acceptableFileType, camelCase, caseCamel, caseConvert, caseKebab, casePascal, caseSnake, clamp, compose, _curryMore as curry, debounce, deepClone, defer, fastClone, getAcceptableExtByMIME, getAcceptableMIMEByExt, getGlobalThis, getTag, isArray, isArrayBuffer, isArrayLike, isBigInt, isBigInt64Array, isBigUint64Array, isBoolean, isBuffer, isDataView, isDate, isEmpty, isFile, isFloat32Array, isFloat64Array, isFormData, isFunction, isInt16Array, isInt32Array, isInt8Array, isInteger, isIterable, isMap, isNil, isNull, isNumber, isObject, isPlaceholder, isPrimitive, isPromise, isPromiseLike, isRegExp, isSet, isString, isSymbol, isTypedArray, isUint16Array, isUint32Array, isUint8Array, isUint8ClampedArray, isUndefined, isWeakMap, isWeakSet, isWrapperBigInt, isWrapperBoolean, isWrapperNumber, isWrapperObject, isWrapperString, isWrapperSymbol, kebabCase, memo, noop, not, parallel, pascalCase, pass, passWith, pipe, randomBase32String, randomChoice, randomHexString, randomInt, randomIntFloor, randomString, range, remove, retry, shuffle, sleep, snakeCase, splitWords, throttle, titleCase, tryit, ulid, uuidNil, uuidV4, withResolvers };
package/lib/index.mjs CHANGED
@@ -65,11 +65,37 @@ function remove(arr, ...item) {
65
65
  return res;
66
66
  }
67
67
 
68
- function sleep(time = 1000) {
69
- return new Promise((res) => {
70
- setTimeout(res, time);
71
- });
72
- }
68
+ /******************************************************************************
69
+ Copyright (c) Microsoft Corporation.
70
+
71
+ Permission to use, copy, modify, and/or distribute this software for any
72
+ purpose with or without fee is hereby granted.
73
+
74
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
75
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
76
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
77
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
78
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
79
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
80
+ PERFORMANCE OF THIS SOFTWARE.
81
+ ***************************************************************************** */
82
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
83
+
84
+
85
+ function __awaiter(thisArg, _arguments, P, generator) {
86
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
87
+ return new (P || (P = Promise))(function (resolve, reject) {
88
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
89
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
90
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
91
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
92
+ });
93
+ }
94
+
95
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
96
+ var e = new Error(message);
97
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
98
+ };
73
99
 
74
100
  const isArray = Array.isArray;
75
101
 
@@ -169,7 +195,7 @@ function isNumber(value) {
169
195
  }
170
196
 
171
197
  function isPrimitive(value) {
172
- return value == null || (typeof value !== 'object' && typeof value !== 'function');
198
+ return value === undefined || value === null || (typeof value !== 'object' && typeof value !== 'function');
173
199
  }
174
200
 
175
201
  function isPromise(value) {
@@ -290,17 +316,37 @@ function tryit(fn) {
290
316
  };
291
317
  }
292
318
 
293
- const noop = function noop() { };
294
-
295
- function withResolvers(PromiseLike = Promise) {
296
- let promise;
297
- let resolve = noop;
298
- let reject = noop;
299
- promise = new PromiseLike((res, rej) => {
300
- resolve = res;
301
- reject = rej;
319
+ function defer(asyncFunction, options) {
320
+ return __awaiter(this, void 0, void 0, function* () {
321
+ const queue = [];
322
+ const { rethrow = false } = options || {};
323
+ const defaultOption = { rethrow };
324
+ const cleanUp = (fn, options) => {
325
+ queue.push({ fn, opt: Object.assign(defaultOption, options) });
326
+ return queue.length - 1;
327
+ };
328
+ const cancelCleanUp = (fnOrIndex) => {
329
+ if (isInteger(fnOrIndex) && fnOrIndex > -1)
330
+ queue[fnOrIndex] = null;
331
+ else if (isFunction(fnOrIndex)) {
332
+ const i = queue.findIndex((item) => item && item.fn === fnOrIndex);
333
+ if (i > -1)
334
+ queue[i] = null;
335
+ }
336
+ };
337
+ const [err, res] = yield tryit(asyncFunction)(cleanUp, cancelCleanUp);
338
+ for (const item of queue) {
339
+ if (!item)
340
+ continue;
341
+ const { fn, opt } = item;
342
+ const [cleanUpErr] = yield tryit(fn)(err);
343
+ if (cleanUpErr && opt.rethrow)
344
+ throw cleanUpErr;
345
+ }
346
+ if (err)
347
+ throw err;
348
+ return res;
302
349
  });
303
- return { promise, resolve, reject };
304
350
  }
305
351
 
306
352
  function clamp(num, min, max, options) {
@@ -320,6 +366,96 @@ function clamp(num, min, max, options) {
320
366
  return num < min ? defaultMin : num > max ? defaultMax : num;
321
367
  }
322
368
 
369
+ function parallel(args, fn, options) {
370
+ return __awaiter(this, void 0, void 0, function* () {
371
+ if (!args.length)
372
+ return [];
373
+ const { limit: _limit = 5 } = options || {};
374
+ const limit = clamp(Math.floor(_limit), 1, 100);
375
+ let current = 0;
376
+ const results = [];
377
+ const errors = [];
378
+ const asyncFn = tryit(fn);
379
+ const processor = () => __awaiter(this, void 0, void 0, function* () {
380
+ while (current < args.length) {
381
+ const index = current++;
382
+ const [err, result] = yield asyncFn(args[index]);
383
+ if (err)
384
+ errors.push({ index, error: err });
385
+ else
386
+ results[index] = result;
387
+ }
388
+ });
389
+ const tasks = [];
390
+ for (let i = 0; i < Math.min(args.length, limit); i++) {
391
+ tasks.push(processor());
392
+ }
393
+ yield Promise.all(tasks);
394
+ if (errors.length) {
395
+ throw new Error(`Parallel execution failed on index: ${errors.map((e) => e.index).join(', ')}`, { cause: errors });
396
+ }
397
+ return results;
398
+ });
399
+ }
400
+
401
+ function sleep(time = 1000) {
402
+ return new Promise((res) => {
403
+ setTimeout(res, time);
404
+ });
405
+ }
406
+
407
+ function retry(asyncFunction, option) {
408
+ return __awaiter(this, void 0, void 0, function* () {
409
+ let retryCounts = 0;
410
+ const times = isNumber(option === null || option === void 0 ? void 0 : option.times) ? option.times : 3;
411
+ const delay = isFunction(option === null || option === void 0 ? void 0 : option.delay)
412
+ ? option.delay
413
+ : isNumber(option === null || option === void 0 ? void 0 : option.delay)
414
+ ? () => option.delay
415
+ : null;
416
+ const gap = isFunction(option === null || option === void 0 ? void 0 : option.gap) ? option.gap : isNumber(option === null || option === void 0 ? void 0 : option.gap) ? () => option.gap : null;
417
+ let lastRunTime = 0;
418
+ const getDelayTime = !option || (!delay && !gap)
419
+ ? () => 0
420
+ : gap
421
+ ? (retryCounts) => {
422
+ const time = gap(retryCounts);
423
+ return time - Date.now() + lastRunTime;
424
+ }
425
+ : delay;
426
+ while (1) {
427
+ lastRunTime = Date.now();
428
+ const [err, res] = yield tryit(asyncFunction)((err) => {
429
+ throw { $$exit_retry: err };
430
+ });
431
+ if (!err)
432
+ return res;
433
+ retryCounts++;
434
+ if (err && err.$$exit_retry)
435
+ throw err.$$exit_retry;
436
+ if (retryCounts >= times)
437
+ throw err;
438
+ const delayTime = getDelayTime(retryCounts);
439
+ if (delayTime > 0)
440
+ yield sleep(delayTime);
441
+ }
442
+ throw new Error('retry failed');
443
+ });
444
+ }
445
+
446
+ const noop = function noop() { };
447
+
448
+ function withResolvers(PromiseLike = Promise) {
449
+ let promise;
450
+ let resolve = noop;
451
+ let reject = noop;
452
+ promise = new PromiseLike((res, rej) => {
453
+ resolve = res;
454
+ reject = rej;
455
+ });
456
+ return { promise, resolve, reject };
457
+ }
458
+
323
459
  const mimeMap = {
324
460
  application: {
325
461
  acrobat: ['pdf'],
@@ -1319,9 +1455,10 @@ function _curryMore(fn) {
1319
1455
  }
1320
1456
 
1321
1457
  function _throttle(fn, delay, options) {
1322
- var _a, _b;
1458
+ var _a, _b, _c;
1323
1459
  const trailing = (_a = options === null || options === void 0 ? void 0 : options.trailing) !== null && _a !== void 0 ? _a : false;
1324
- const leading = (_b = options === null || options === void 0 ? void 0 : options.leading) !== null && _b !== void 0 ? _b : true;
1460
+ const trailingRunLast = (_b = options === null || options === void 0 ? void 0 : options.trailingRunLast) !== null && _b !== void 0 ? _b : true;
1461
+ const leading = (_c = options === null || options === void 0 ? void 0 : options.leading) !== null && _c !== void 0 ? _c : true;
1325
1462
  let timer = null;
1326
1463
  let lastTime = 0;
1327
1464
  const clearTimer = () => {
@@ -1341,6 +1478,9 @@ function _throttle(fn, delay, options) {
1341
1478
  lastTime = now;
1342
1479
  clearTimer();
1343
1480
  }
1481
+ if (timeGap < 0 && trailing && trailingRunLast && timer) {
1482
+ clearTimer();
1483
+ }
1344
1484
  if (timeGap >= 0 && leading) {
1345
1485
  fn.apply(this, args);
1346
1486
  }
@@ -1358,7 +1498,7 @@ function debounce(fn, delay, options) {
1358
1498
  if (!isNumber(delay) || !isFinite(delay) || delay <= 0) {
1359
1499
  throw new Error('Invalid delay parameter');
1360
1500
  }
1361
- return _throttle(fn, delay, Object.assign({ trailing: true, leading: false }, options));
1501
+ return _throttle(fn, delay, Object.assign({ trailing: true, leading: false, trailingRunLast: true }, options));
1362
1502
  }
1363
1503
 
1364
1504
  function _cloneArray(obj, map, cloner, ...args) {
@@ -1632,4 +1772,4 @@ function throttle(fn, delay, options) {
1632
1772
  return _throttle(fn, delay, Object.assign({ trailing: false, leading: true }, options));
1633
1773
  }
1634
1774
 
1635
- export { _, acceptableFileName, acceptableFileType, camelCase, caseCamel, caseConvert, caseKebab, casePascal, caseSnake, clamp, compose, _curryMore as curry, debounce, deepClone, fastClone, getAcceptableExtByMIME, getAcceptableMIMEByExt, getGlobalThis, getTag, isArray, isArrayBuffer, isArrayLike, isBigInt, isBigInt64Array, isBigUint64Array, isBoolean, isBuffer, isDataView, isDate, isEmpty, isFile, isFloat32Array, isFloat64Array, isFormData, isFunction, isInt16Array, isInt32Array, isInt8Array, isInteger, isIterable, isMap, isNil, isNull, isNumber, isObject, isPlaceholder, isPrimitive, isPromise, isPromiseLike, isRegExp, isSet, isString, isSymbol, isTypedArray, isUint16Array, isUint32Array, isUint8Array, isUint8ClampedArray, isUndefined, isWeakMap, isWeakSet, isWrapperBigInt, isWrapperBoolean, isWrapperNumber, isWrapperObject, isWrapperString, isWrapperSymbol, kebabCase, memo, noop, not, pascalCase, pass, passWith, pipe, randomBase32String, randomChoice, randomHexString, randomInt, randomIntFloor, randomString, range, remove, shuffle, sleep, snakeCase, splitWords, throttle, titleCase, tryit, ulid, uuidNil, uuidV4, withResolvers };
1775
+ export { _, acceptableFileName, acceptableFileType, camelCase, caseCamel, caseConvert, caseKebab, casePascal, caseSnake, clamp, compose, _curryMore as curry, debounce, deepClone, defer, fastClone, getAcceptableExtByMIME, getAcceptableMIMEByExt, getGlobalThis, getTag, isArray, isArrayBuffer, isArrayLike, isBigInt, isBigInt64Array, isBigUint64Array, isBoolean, isBuffer, isDataView, isDate, isEmpty, isFile, isFloat32Array, isFloat64Array, isFormData, isFunction, isInt16Array, isInt32Array, isInt8Array, isInteger, isIterable, isMap, isNil, isNull, isNumber, isObject, isPlaceholder, isPrimitive, isPromise, isPromiseLike, isRegExp, isSet, isString, isSymbol, isTypedArray, isUint16Array, isUint32Array, isUint8Array, isUint8ClampedArray, isUndefined, isWeakMap, isWeakSet, isWrapperBigInt, isWrapperBoolean, isWrapperNumber, isWrapperObject, isWrapperString, isWrapperSymbol, kebabCase, memo, noop, not, parallel, pascalCase, pass, passWith, pipe, randomBase32String, randomChoice, randomHexString, randomInt, randomIntFloor, randomString, range, remove, retry, shuffle, sleep, snakeCase, splitWords, throttle, titleCase, tryit, ulid, uuidNil, uuidV4, withResolvers };
package/lib/index.umd.js CHANGED
@@ -71,11 +71,37 @@ See the Mulan PSL v2 for more details.
71
71
  return res;
72
72
  }
73
73
 
74
- function sleep(time = 1000) {
75
- return new Promise((res) => {
76
- setTimeout(res, time);
77
- });
78
- }
74
+ /******************************************************************************
75
+ Copyright (c) Microsoft Corporation.
76
+
77
+ Permission to use, copy, modify, and/or distribute this software for any
78
+ purpose with or without fee is hereby granted.
79
+
80
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
81
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
82
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
83
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
84
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
85
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
86
+ PERFORMANCE OF THIS SOFTWARE.
87
+ ***************************************************************************** */
88
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
89
+
90
+
91
+ function __awaiter(thisArg, _arguments, P, generator) {
92
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
93
+ return new (P || (P = Promise))(function (resolve, reject) {
94
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
95
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
96
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
97
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
98
+ });
99
+ }
100
+
101
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
102
+ var e = new Error(message);
103
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
104
+ };
79
105
 
80
106
  const isArray = Array.isArray;
81
107
 
@@ -175,7 +201,7 @@ See the Mulan PSL v2 for more details.
175
201
  }
176
202
 
177
203
  function isPrimitive(value) {
178
- return value == null || (typeof value !== 'object' && typeof value !== 'function');
204
+ return value === undefined || value === null || (typeof value !== 'object' && typeof value !== 'function');
179
205
  }
180
206
 
181
207
  function isPromise(value) {
@@ -296,17 +322,37 @@ See the Mulan PSL v2 for more details.
296
322
  };
297
323
  }
298
324
 
299
- const noop = function noop() { };
300
-
301
- function withResolvers(PromiseLike = Promise) {
302
- let promise;
303
- let resolve = noop;
304
- let reject = noop;
305
- promise = new PromiseLike((res, rej) => {
306
- resolve = res;
307
- reject = rej;
325
+ function defer(asyncFunction, options) {
326
+ return __awaiter(this, void 0, void 0, function* () {
327
+ const queue = [];
328
+ const { rethrow = false } = options || {};
329
+ const defaultOption = { rethrow };
330
+ const cleanUp = (fn, options) => {
331
+ queue.push({ fn, opt: Object.assign(defaultOption, options) });
332
+ return queue.length - 1;
333
+ };
334
+ const cancelCleanUp = (fnOrIndex) => {
335
+ if (isInteger(fnOrIndex) && fnOrIndex > -1)
336
+ queue[fnOrIndex] = null;
337
+ else if (isFunction(fnOrIndex)) {
338
+ const i = queue.findIndex((item) => item && item.fn === fnOrIndex);
339
+ if (i > -1)
340
+ queue[i] = null;
341
+ }
342
+ };
343
+ const [err, res] = yield tryit(asyncFunction)(cleanUp, cancelCleanUp);
344
+ for (const item of queue) {
345
+ if (!item)
346
+ continue;
347
+ const { fn, opt } = item;
348
+ const [cleanUpErr] = yield tryit(fn)(err);
349
+ if (cleanUpErr && opt.rethrow)
350
+ throw cleanUpErr;
351
+ }
352
+ if (err)
353
+ throw err;
354
+ return res;
308
355
  });
309
- return { promise, resolve, reject };
310
356
  }
311
357
 
312
358
  function clamp(num, min, max, options) {
@@ -326,6 +372,96 @@ See the Mulan PSL v2 for more details.
326
372
  return num < min ? defaultMin : num > max ? defaultMax : num;
327
373
  }
328
374
 
375
+ function parallel(args, fn, options) {
376
+ return __awaiter(this, void 0, void 0, function* () {
377
+ if (!args.length)
378
+ return [];
379
+ const { limit: _limit = 5 } = options || {};
380
+ const limit = clamp(Math.floor(_limit), 1, 100);
381
+ let current = 0;
382
+ const results = [];
383
+ const errors = [];
384
+ const asyncFn = tryit(fn);
385
+ const processor = () => __awaiter(this, void 0, void 0, function* () {
386
+ while (current < args.length) {
387
+ const index = current++;
388
+ const [err, result] = yield asyncFn(args[index]);
389
+ if (err)
390
+ errors.push({ index, error: err });
391
+ else
392
+ results[index] = result;
393
+ }
394
+ });
395
+ const tasks = [];
396
+ for (let i = 0; i < Math.min(args.length, limit); i++) {
397
+ tasks.push(processor());
398
+ }
399
+ yield Promise.all(tasks);
400
+ if (errors.length) {
401
+ throw new Error(`Parallel execution failed on index: ${errors.map((e) => e.index).join(', ')}`, { cause: errors });
402
+ }
403
+ return results;
404
+ });
405
+ }
406
+
407
+ function sleep(time = 1000) {
408
+ return new Promise((res) => {
409
+ setTimeout(res, time);
410
+ });
411
+ }
412
+
413
+ function retry(asyncFunction, option) {
414
+ return __awaiter(this, void 0, void 0, function* () {
415
+ let retryCounts = 0;
416
+ const times = isNumber(option === null || option === void 0 ? void 0 : option.times) ? option.times : 3;
417
+ const delay = isFunction(option === null || option === void 0 ? void 0 : option.delay)
418
+ ? option.delay
419
+ : isNumber(option === null || option === void 0 ? void 0 : option.delay)
420
+ ? () => option.delay
421
+ : null;
422
+ const gap = isFunction(option === null || option === void 0 ? void 0 : option.gap) ? option.gap : isNumber(option === null || option === void 0 ? void 0 : option.gap) ? () => option.gap : null;
423
+ let lastRunTime = 0;
424
+ const getDelayTime = !option || (!delay && !gap)
425
+ ? () => 0
426
+ : gap
427
+ ? (retryCounts) => {
428
+ const time = gap(retryCounts);
429
+ return time - Date.now() + lastRunTime;
430
+ }
431
+ : delay;
432
+ while (1) {
433
+ lastRunTime = Date.now();
434
+ const [err, res] = yield tryit(asyncFunction)((err) => {
435
+ throw { $$exit_retry: err };
436
+ });
437
+ if (!err)
438
+ return res;
439
+ retryCounts++;
440
+ if (err && err.$$exit_retry)
441
+ throw err.$$exit_retry;
442
+ if (retryCounts >= times)
443
+ throw err;
444
+ const delayTime = getDelayTime(retryCounts);
445
+ if (delayTime > 0)
446
+ yield sleep(delayTime);
447
+ }
448
+ throw new Error('retry failed');
449
+ });
450
+ }
451
+
452
+ const noop = function noop() { };
453
+
454
+ function withResolvers(PromiseLike = Promise) {
455
+ let promise;
456
+ let resolve = noop;
457
+ let reject = noop;
458
+ promise = new PromiseLike((res, rej) => {
459
+ resolve = res;
460
+ reject = rej;
461
+ });
462
+ return { promise, resolve, reject };
463
+ }
464
+
329
465
  const mimeMap = {
330
466
  application: {
331
467
  acrobat: ['pdf'],
@@ -1325,9 +1461,10 @@ See the Mulan PSL v2 for more details.
1325
1461
  }
1326
1462
 
1327
1463
  function _throttle(fn, delay, options) {
1328
- var _a, _b;
1464
+ var _a, _b, _c;
1329
1465
  const trailing = (_a = options === null || options === void 0 ? void 0 : options.trailing) !== null && _a !== void 0 ? _a : false;
1330
- const leading = (_b = options === null || options === void 0 ? void 0 : options.leading) !== null && _b !== void 0 ? _b : true;
1466
+ const trailingRunLast = (_b = options === null || options === void 0 ? void 0 : options.trailingRunLast) !== null && _b !== void 0 ? _b : true;
1467
+ const leading = (_c = options === null || options === void 0 ? void 0 : options.leading) !== null && _c !== void 0 ? _c : true;
1331
1468
  let timer = null;
1332
1469
  let lastTime = 0;
1333
1470
  const clearTimer = () => {
@@ -1347,6 +1484,9 @@ See the Mulan PSL v2 for more details.
1347
1484
  lastTime = now;
1348
1485
  clearTimer();
1349
1486
  }
1487
+ if (timeGap < 0 && trailing && trailingRunLast && timer) {
1488
+ clearTimer();
1489
+ }
1350
1490
  if (timeGap >= 0 && leading) {
1351
1491
  fn.apply(this, args);
1352
1492
  }
@@ -1364,7 +1504,7 @@ See the Mulan PSL v2 for more details.
1364
1504
  if (!isNumber(delay) || !isFinite(delay) || delay <= 0) {
1365
1505
  throw new Error('Invalid delay parameter');
1366
1506
  }
1367
- return _throttle(fn, delay, Object.assign({ trailing: true, leading: false }, options));
1507
+ return _throttle(fn, delay, Object.assign({ trailing: true, leading: false, trailingRunLast: true }, options));
1368
1508
  }
1369
1509
 
1370
1510
  function _cloneArray(obj, map, cloner, ...args) {
@@ -1652,6 +1792,7 @@ See the Mulan PSL v2 for more details.
1652
1792
  exports.curry = _curryMore;
1653
1793
  exports.debounce = debounce;
1654
1794
  exports.deepClone = deepClone;
1795
+ exports.defer = defer;
1655
1796
  exports.fastClone = fastClone;
1656
1797
  exports.getAcceptableExtByMIME = getAcceptableExtByMIME;
1657
1798
  exports.getAcceptableMIMEByExt = getAcceptableMIMEByExt;
@@ -1709,6 +1850,7 @@ See the Mulan PSL v2 for more details.
1709
1850
  exports.memo = memo;
1710
1851
  exports.noop = noop;
1711
1852
  exports.not = not;
1853
+ exports.parallel = parallel;
1712
1854
  exports.pascalCase = pascalCase;
1713
1855
  exports.pass = pass;
1714
1856
  exports.passWith = passWith;
@@ -1721,6 +1863,7 @@ See the Mulan PSL v2 for more details.
1721
1863
  exports.randomString = randomString;
1722
1864
  exports.range = range;
1723
1865
  exports.remove = remove;
1866
+ exports.retry = retry;
1724
1867
  exports.shuffle = shuffle;
1725
1868
  exports.sleep = sleep;
1726
1869
  exports.snakeCase = snakeCase;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreslash",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Foreslash is a Javascript utilities lib which contains plenty of practical functions.",
5
5
  "author": "moushu",
6
6
  "license": "Mulan PSL v2",