es-toolkit 1.47.1 → 1.48.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/browser.global.js +3 -3
  3. package/dist/compat/array/difference.js +1 -2
  4. package/dist/compat/array/difference.mjs +1 -2
  5. package/dist/compat/array/sortedIndex.js +1 -1
  6. package/dist/compat/array/sortedIndex.mjs +1 -1
  7. package/dist/compat/compat.js +1 -1
  8. package/dist/compat/compat.mjs +1 -1
  9. package/dist/compat/index.js +1 -1
  10. package/dist/compat/index.mjs +1 -1
  11. package/dist/compat/math/parseInt.js +2 -1
  12. package/dist/compat/math/parseInt.mjs +2 -1
  13. package/dist/compat/math/random.d.mts +3 -3
  14. package/dist/compat/math/random.d.ts +3 -3
  15. package/dist/compat/object/pick.js +6 -5
  16. package/dist/compat/object/pick.mjs +6 -5
  17. package/dist/compat/predicate/isMatchWith.js +9 -6
  18. package/dist/compat/predicate/isMatchWith.mjs +9 -6
  19. package/dist/compat/predicate/isNaN.js +2 -1
  20. package/dist/compat/predicate/isNaN.mjs +2 -1
  21. package/dist/compat/predicate/isNumber.js +4 -1
  22. package/dist/compat/predicate/isNumber.mjs +4 -1
  23. package/dist/compat/util/toPath.js +3 -4
  24. package/dist/compat/util/toPath.mjs +3 -4
  25. package/dist/function/debounce.d.mts +1 -0
  26. package/dist/function/debounce.d.ts +1 -0
  27. package/dist/function/debounce.js +1 -0
  28. package/dist/function/debounce.mjs +1 -0
  29. package/dist/object/toMerged.js +2 -1
  30. package/dist/object/toMerged.mjs +2 -1
  31. package/dist/promise/timeout.d.mts +25 -2
  32. package/dist/promise/timeout.d.ts +25 -2
  33. package/dist/promise/timeout.js +31 -5
  34. package/dist/promise/timeout.mjs +31 -5
  35. package/dist/promise/withTimeout.d.mts +19 -1
  36. package/dist/promise/withTimeout.d.ts +19 -1
  37. package/dist/promise/withTimeout.js +15 -2
  38. package/dist/promise/withTimeout.mjs +15 -2
  39. package/package.json +4 -3
@@ -24,14 +24,15 @@ const require_set = require("./set.js");
24
24
  * // each path can be passed individually as an argument
25
25
  * const obj = { a: 1, b: 2, c: 3 };
26
26
  * const result = pick(obj, 'a', 'c');
27
+ * // result will be { a: 1, c: 3 }
27
28
  *
28
29
  * // pick a key over a path
29
30
  * const obj = { 'a.b': 1, a: { b: 2 } };
30
31
  * const result = pick(obj, 'a.b');
31
32
  * // result will be { 'a.b': 1 }
32
33
  */
33
- function pick(obj, ...keysArr) {
34
- if (require_isNil.isNil(obj)) return {};
34
+ function pick(object, ...keysArr) {
35
+ if (require_isNil.isNil(object)) return {};
35
36
  const result = {};
36
37
  for (let i = 0; i < keysArr.length; i++) {
37
38
  let keys = keysArr[i];
@@ -47,9 +48,9 @@ function pick(obj, ...keysArr) {
47
48
  break;
48
49
  }
49
50
  for (const key of keys) {
50
- const value = require_get.get(obj, key);
51
- if (value === void 0 && !require_has.has(obj, key)) continue;
52
- if (typeof key === "string" && Object.hasOwn(obj, key)) result[key] = value;
51
+ const value = require_get.get(object, key);
52
+ if (value === void 0 && !require_has.has(object, key)) continue;
53
+ if (typeof key === "string" && Object.hasOwn(object, key)) result[key] = value;
53
54
  else require_set.set(result, key, value);
54
55
  }
55
56
  }
@@ -24,14 +24,15 @@ import { set } from "./set.mjs";
24
24
  * // each path can be passed individually as an argument
25
25
  * const obj = { a: 1, b: 2, c: 3 };
26
26
  * const result = pick(obj, 'a', 'c');
27
+ * // result will be { a: 1, c: 3 }
27
28
  *
28
29
  * // pick a key over a path
29
30
  * const obj = { 'a.b': 1, a: { b: 2 } };
30
31
  * const result = pick(obj, 'a.b');
31
32
  * // result will be { 'a.b': 1 }
32
33
  */
33
- function pick(obj, ...keysArr) {
34
- if (isNil(obj)) return {};
34
+ function pick(object, ...keysArr) {
35
+ if (isNil(object)) return {};
35
36
  const result = {};
36
37
  for (let i = 0; i < keysArr.length; i++) {
37
38
  let keys = keysArr[i];
@@ -47,9 +48,9 @@ function pick(obj, ...keysArr) {
47
48
  break;
48
49
  }
49
50
  for (const key of keys) {
50
- const value = get(obj, key);
51
- if (value === void 0 && !has(obj, key)) continue;
52
- if (typeof key === "string" && Object.hasOwn(obj, key)) result[key] = value;
51
+ const value = get(object, key);
52
+ if (value === void 0 && !has(object, key)) continue;
53
+ if (typeof key === "string" && Object.hasOwn(object, key)) result[key] = value;
53
54
  else set(result, key, value);
54
55
  }
55
56
  }
@@ -77,20 +77,23 @@ function isMatchWith(target, source, compare) {
77
77
  return isMatchWithInternal(target, source, function doesMatch(objValue, srcValue, key, object, source, stack) {
78
78
  const isEqual = compare(objValue, srcValue, key, object, source, stack);
79
79
  if (isEqual !== void 0) return Boolean(isEqual);
80
- return isMatchWithInternal(objValue, srcValue, doesMatch, stack);
81
- }, /* @__PURE__ */ new Map());
80
+ return isMatchWithInternal(objValue, srcValue, doesMatch, stack, false);
81
+ }, /* @__PURE__ */ new Map(), true);
82
82
  }
83
- function isMatchWithInternal(target, source, compare, stack) {
83
+ function isMatchWithInternal(target, source, compare, stack, isRoot = false) {
84
84
  if (source === target) return true;
85
85
  switch (typeof source) {
86
86
  case "object": return isObjectMatch(target, source, compare, stack);
87
87
  case "function":
88
- if (Object.keys(source).length > 0) return isMatchWithInternal(target, { ...source }, compare, stack);
88
+ if (Object.keys(source).length > 0) return isMatchWithInternal(target, { ...source }, compare, stack, isRoot);
89
89
  return require_isEqualsSameValueZero.isEqualsSameValueZero(target, source);
90
90
  default:
91
91
  if (!require_isObject.isObject(target)) return require_isEqualsSameValueZero.isEqualsSameValueZero(target, source);
92
- if (typeof source === "string") return source === "";
93
- return true;
92
+ if (isRoot) {
93
+ if (typeof source === "string") return source === "";
94
+ return true;
95
+ }
96
+ return require_isEqualsSameValueZero.isEqualsSameValueZero(target, source);
94
97
  }
95
98
  }
96
99
  function isObjectMatch(target, source, compare, stack) {
@@ -77,20 +77,23 @@ function isMatchWith(target, source, compare) {
77
77
  return isMatchWithInternal(target, source, function doesMatch(objValue, srcValue, key, object, source, stack) {
78
78
  const isEqual = compare(objValue, srcValue, key, object, source, stack);
79
79
  if (isEqual !== void 0) return Boolean(isEqual);
80
- return isMatchWithInternal(objValue, srcValue, doesMatch, stack);
81
- }, /* @__PURE__ */ new Map());
80
+ return isMatchWithInternal(objValue, srcValue, doesMatch, stack, false);
81
+ }, /* @__PURE__ */ new Map(), true);
82
82
  }
83
- function isMatchWithInternal(target, source, compare, stack) {
83
+ function isMatchWithInternal(target, source, compare, stack, isRoot = false) {
84
84
  if (source === target) return true;
85
85
  switch (typeof source) {
86
86
  case "object": return isObjectMatch(target, source, compare, stack);
87
87
  case "function":
88
- if (Object.keys(source).length > 0) return isMatchWithInternal(target, { ...source }, compare, stack);
88
+ if (Object.keys(source).length > 0) return isMatchWithInternal(target, { ...source }, compare, stack, isRoot);
89
89
  return isEqualsSameValueZero(target, source);
90
90
  default:
91
91
  if (!isObject(target)) return isEqualsSameValueZero(target, source);
92
- if (typeof source === "string") return source === "";
93
- return true;
92
+ if (isRoot) {
93
+ if (typeof source === "string") return source === "";
94
+ return true;
95
+ }
96
+ return isEqualsSameValueZero(target, source);
94
97
  }
95
98
  }
96
99
  function isObjectMatch(target, source, compare, stack) {
@@ -1,3 +1,4 @@
1
+ const require_isNumber = require("./isNumber.js");
1
2
  //#region src/compat/predicate/isNaN.ts
2
3
  /**
3
4
  * Checks if the value is NaN.
@@ -12,7 +13,7 @@
12
13
  * isNaN(undefined); // false
13
14
  */
14
15
  function isNaN(value) {
15
- return Number.isNaN(value);
16
+ return require_isNumber.isNumber(value) && Number.isNaN(Number(value));
16
17
  }
17
18
  //#endregion
18
19
  exports.isNaN = isNaN;
@@ -1,3 +1,4 @@
1
+ import { isNumber } from "./isNumber.mjs";
1
2
  //#region src/compat/predicate/isNaN.ts
2
3
  /**
3
4
  * Checks if the value is NaN.
@@ -12,7 +13,7 @@
12
13
  * isNaN(undefined); // false
13
14
  */
14
15
  function isNaN(value) {
15
- return Number.isNaN(value);
16
+ return isNumber(value) && Number.isNaN(Number(value));
16
17
  }
17
18
  //#endregion
18
19
  export { isNaN };
@@ -1,3 +1,6 @@
1
+ const require_getTag = require("../_internal/getTag.js");
2
+ require("../_internal/tags.js");
3
+ const require_isObjectLike = require("./isObjectLike.js");
1
4
  //#region src/compat/predicate/isNumber.ts
2
5
  /**
3
6
  * Checks if a given value is a number.
@@ -17,7 +20,7 @@
17
20
  * console.log(isNumber(value3)); // false
18
21
  */
19
22
  function isNumber(value) {
20
- return typeof value === "number" || value instanceof Number;
23
+ return typeof value === "number" || require_isObjectLike.isObjectLike(value) && require_getTag.getTag(value) === "[object Number]";
21
24
  }
22
25
  //#endregion
23
26
  exports.isNumber = isNumber;
@@ -1,3 +1,6 @@
1
+ import { getTag } from "../_internal/getTag.mjs";
2
+ import "../_internal/tags.mjs";
3
+ import { isObjectLike } from "./isObjectLike.mjs";
1
4
  //#region src/compat/predicate/isNumber.ts
2
5
  /**
3
6
  * Checks if a given value is a number.
@@ -17,7 +20,7 @@
17
20
  * console.log(isNumber(value3)); // false
18
21
  */
19
22
  function isNumber(value) {
20
- return typeof value === "number" || value instanceof Number;
23
+ return typeof value === "number" || isObjectLike(value) && getTag(value) === "[object Number]";
21
24
  }
22
25
  //#endregion
23
26
  export { isNumber };
@@ -29,10 +29,7 @@ function toPath(deepKey) {
29
29
  let key = "";
30
30
  let quoteChar = "";
31
31
  let bracket = false;
32
- if (deepKey.charCodeAt(0) === 46) {
33
- result.push("");
34
- index++;
35
- }
32
+ if (deepKey.charCodeAt(0) === 46) result.push("");
36
33
  while (index < length) {
37
34
  const char = deepKey[index];
38
35
  if (quoteChar) if (char === "\\" && index + 1 < length) {
@@ -57,6 +54,8 @@ function toPath(deepKey) {
57
54
  result.push(key);
58
55
  key = "";
59
56
  }
57
+ const next = deepKey[index + 1];
58
+ if (next === void 0 || next === ".") result.push("");
60
59
  } else key += char;
61
60
  index++;
62
61
  }
@@ -29,10 +29,7 @@ function toPath(deepKey) {
29
29
  let key = "";
30
30
  let quoteChar = "";
31
31
  let bracket = false;
32
- if (deepKey.charCodeAt(0) === 46) {
33
- result.push("");
34
- index++;
35
- }
32
+ if (deepKey.charCodeAt(0) === 46) result.push("");
36
33
  while (index < length) {
37
34
  const char = deepKey[index];
38
35
  if (quoteChar) if (char === "\\" && index + 1 < length) {
@@ -57,6 +54,8 @@ function toPath(deepKey) {
57
54
  result.push(key);
58
55
  key = "";
59
56
  }
57
+ const next = deepKey[index + 1];
58
+ if (next === void 0 || next === ".") result.push("");
60
59
  } else key += char;
61
60
  index++;
62
61
  }
@@ -43,6 +43,7 @@ interface DebouncedFunction<F extends (...args: any[]) => void> {
43
43
  * @param debounceMs - The number of milliseconds to delay.
44
44
  * @param options - The options object
45
45
  * @param options.signal - An optional AbortSignal to cancel the debounced function.
46
+ * @param options.edges - An optional array specifying whether the function should be invoked on the leading edge, trailing edge, or both.
46
47
  * @returns A new debounced function with a `cancel` method.
47
48
  *
48
49
  * @example
@@ -43,6 +43,7 @@ interface DebouncedFunction<F extends (...args: any[]) => void> {
43
43
  * @param debounceMs - The number of milliseconds to delay.
44
44
  * @param options - The options object
45
45
  * @param options.signal - An optional AbortSignal to cancel the debounced function.
46
+ * @param options.edges - An optional array specifying whether the function should be invoked on the leading edge, trailing edge, or both.
46
47
  * @returns A new debounced function with a `cancel` method.
47
48
  *
48
49
  * @example
@@ -9,6 +9,7 @@
9
9
  * @param debounceMs - The number of milliseconds to delay.
10
10
  * @param options - The options object
11
11
  * @param options.signal - An optional AbortSignal to cancel the debounced function.
12
+ * @param options.edges - An optional array specifying whether the function should be invoked on the leading edge, trailing edge, or both.
12
13
  * @returns A new debounced function with a `cancel` method.
13
14
  *
14
15
  * @example
@@ -9,6 +9,7 @@
9
9
  * @param debounceMs - The number of milliseconds to delay.
10
10
  * @param options - The options object
11
11
  * @param options.signal - An optional AbortSignal to cancel the debounced function.
12
+ * @param options.edges - An optional array specifying whether the function should be invoked on the leading edge, trailing edge, or both.
12
13
  * @returns A new debounced function with a `cancel` method.
13
14
  *
14
15
  * @example
@@ -1,4 +1,5 @@
1
1
  const require_clone = require("./clone.js");
2
+ const require_cloneDeep = require("./cloneDeep.js");
2
3
  const require_isPlainObject = require("../predicate/isPlainObject.js");
3
4
  const require_mergeWith = require("./mergeWith.js");
4
5
  //#region src/object/toMerged.ts
@@ -45,7 +46,7 @@ const require_mergeWith = require("./mergeWith.js");
45
46
  * // Output: { a: [1, 2, 3] }
46
47
  */
47
48
  function toMerged(target, source) {
48
- return require_mergeWith.mergeWith(require_clone.clone(target), source, function mergeRecursively(targetValue, sourceValue) {
49
+ return require_mergeWith.mergeWith(require_cloneDeep.cloneDeep(target), source, function mergeRecursively(targetValue, sourceValue) {
49
50
  if (Array.isArray(sourceValue)) if (Array.isArray(targetValue)) return require_mergeWith.mergeWith(require_clone.clone(targetValue), sourceValue, mergeRecursively);
50
51
  else return require_mergeWith.mergeWith([], sourceValue, mergeRecursively);
51
52
  else if (require_isPlainObject.isPlainObject(sourceValue)) if (require_isPlainObject.isPlainObject(targetValue)) return require_mergeWith.mergeWith(require_clone.clone(targetValue), sourceValue, mergeRecursively);
@@ -1,4 +1,5 @@
1
1
  import { clone } from "./clone.mjs";
2
+ import { cloneDeep } from "./cloneDeep.mjs";
2
3
  import { isPlainObject } from "../predicate/isPlainObject.mjs";
3
4
  import { mergeWith } from "./mergeWith.mjs";
4
5
  //#region src/object/toMerged.ts
@@ -45,7 +46,7 @@ import { mergeWith } from "./mergeWith.mjs";
45
46
  * // Output: { a: [1, 2, 3] }
46
47
  */
47
48
  function toMerged(target, source) {
48
- return mergeWith(clone(target), source, function mergeRecursively(targetValue, sourceValue) {
49
+ return mergeWith(cloneDeep(target), source, function mergeRecursively(targetValue, sourceValue) {
49
50
  if (Array.isArray(sourceValue)) if (Array.isArray(targetValue)) return mergeWith(clone(targetValue), sourceValue, mergeRecursively);
50
51
  else return mergeWith([], sourceValue, mergeRecursively);
51
52
  else if (isPlainObject(sourceValue)) if (isPlainObject(targetValue)) return mergeWith(clone(targetValue), sourceValue, mergeRecursively);
@@ -1,9 +1,20 @@
1
1
  //#region src/promise/timeout.d.ts
2
+ interface TimeoutOptions {
3
+ signal?: AbortSignal;
4
+ }
2
5
  /**
3
6
  * Returns a promise that rejects with a `TimeoutError` after a specified delay.
4
7
  *
8
+ * You can pass an `AbortSignal` to cancel the timeout. Unlike most `AbortSignal`-aware
9
+ * APIs, aborting does **not** reject the promise. A `timeout` only exists to lose a
10
+ * `Promise.race`, so cancelling it leaves the promise pending forever, allowing the
11
+ * operation it guards to settle on its own. The underlying timer and abort listener
12
+ * are cleared on abort, so nothing is leaked.
13
+ *
5
14
  * @param ms - The delay duration in milliseconds.
6
- * @returns A promise that rejects with a `TimeoutError` after the specified delay.
15
+ * @param options - The options object.
16
+ * @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the returned promise never settles.
17
+ * @returns A promise that rejects with a `TimeoutError` after the specified delay, or never settles if aborted.
7
18
  * @throws {TimeoutError} Throws a `TimeoutError` after the specified delay.
8
19
  *
9
20
  * @example
@@ -12,7 +23,19 @@
12
23
  * } catch (error) {
13
24
  * console.error(error); // Will log 'The operation was timed out'
14
25
  * }
26
+ *
27
+ * @example
28
+ * // Cancelling the timeout lifts the time limit instead of throwing.
29
+ * const controller = new AbortController();
30
+ * setTimeout(() => controller.abort(), 50);
31
+ *
32
+ * const result = await Promise.race([
33
+ * doWork(),
34
+ * timeout(1000, { signal: controller.signal }), // never rejects once aborted
35
+ * ]);
15
36
  */
16
- declare function timeout(ms: number): Promise<never>;
37
+ declare function timeout(ms: number, {
38
+ signal
39
+ }?: TimeoutOptions): Promise<never>;
17
40
  //#endregion
18
41
  export { timeout };
@@ -1,9 +1,20 @@
1
1
  //#region src/promise/timeout.d.ts
2
+ interface TimeoutOptions {
3
+ signal?: AbortSignal;
4
+ }
2
5
  /**
3
6
  * Returns a promise that rejects with a `TimeoutError` after a specified delay.
4
7
  *
8
+ * You can pass an `AbortSignal` to cancel the timeout. Unlike most `AbortSignal`-aware
9
+ * APIs, aborting does **not** reject the promise. A `timeout` only exists to lose a
10
+ * `Promise.race`, so cancelling it leaves the promise pending forever, allowing the
11
+ * operation it guards to settle on its own. The underlying timer and abort listener
12
+ * are cleared on abort, so nothing is leaked.
13
+ *
5
14
  * @param ms - The delay duration in milliseconds.
6
- * @returns A promise that rejects with a `TimeoutError` after the specified delay.
15
+ * @param options - The options object.
16
+ * @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the returned promise never settles.
17
+ * @returns A promise that rejects with a `TimeoutError` after the specified delay, or never settles if aborted.
7
18
  * @throws {TimeoutError} Throws a `TimeoutError` after the specified delay.
8
19
  *
9
20
  * @example
@@ -12,7 +23,19 @@
12
23
  * } catch (error) {
13
24
  * console.error(error); // Will log 'The operation was timed out'
14
25
  * }
26
+ *
27
+ * @example
28
+ * // Cancelling the timeout lifts the time limit instead of throwing.
29
+ * const controller = new AbortController();
30
+ * setTimeout(() => controller.abort(), 50);
31
+ *
32
+ * const result = await Promise.race([
33
+ * doWork(),
34
+ * timeout(1000, { signal: controller.signal }), // never rejects once aborted
35
+ * ]);
15
36
  */
16
- declare function timeout(ms: number): Promise<never>;
37
+ declare function timeout(ms: number, {
38
+ signal
39
+ }?: TimeoutOptions): Promise<never>;
17
40
  //#endregion
18
41
  export { timeout };
@@ -1,11 +1,18 @@
1
1
  const require_TimeoutError = require("../error/TimeoutError.js");
2
- const require_delay = require("./delay.js");
3
2
  //#region src/promise/timeout.ts
4
3
  /**
5
4
  * Returns a promise that rejects with a `TimeoutError` after a specified delay.
6
5
  *
6
+ * You can pass an `AbortSignal` to cancel the timeout. Unlike most `AbortSignal`-aware
7
+ * APIs, aborting does **not** reject the promise. A `timeout` only exists to lose a
8
+ * `Promise.race`, so cancelling it leaves the promise pending forever, allowing the
9
+ * operation it guards to settle on its own. The underlying timer and abort listener
10
+ * are cleared on abort, so nothing is leaked.
11
+ *
7
12
  * @param ms - The delay duration in milliseconds.
8
- * @returns A promise that rejects with a `TimeoutError` after the specified delay.
13
+ * @param options - The options object.
14
+ * @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the returned promise never settles.
15
+ * @returns A promise that rejects with a `TimeoutError` after the specified delay, or never settles if aborted.
9
16
  * @throws {TimeoutError} Throws a `TimeoutError` after the specified delay.
10
17
  *
11
18
  * @example
@@ -14,10 +21,29 @@ const require_delay = require("./delay.js");
14
21
  * } catch (error) {
15
22
  * console.error(error); // Will log 'The operation was timed out'
16
23
  * }
24
+ *
25
+ * @example
26
+ * // Cancelling the timeout lifts the time limit instead of throwing.
27
+ * const controller = new AbortController();
28
+ * setTimeout(() => controller.abort(), 50);
29
+ *
30
+ * const result = await Promise.race([
31
+ * doWork(),
32
+ * timeout(1000, { signal: controller.signal }), // never rejects once aborted
33
+ * ]);
17
34
  */
18
- async function timeout(ms) {
19
- await require_delay.delay(ms);
20
- throw new require_TimeoutError.TimeoutError();
35
+ function timeout(ms, { signal } = {}) {
36
+ return new Promise((_resolve, reject) => {
37
+ const abortHandler = () => {
38
+ clearTimeout(timeoutId);
39
+ };
40
+ if (signal?.aborted) return;
41
+ const timeoutId = setTimeout(() => {
42
+ signal?.removeEventListener("abort", abortHandler);
43
+ reject(new require_TimeoutError.TimeoutError());
44
+ }, ms);
45
+ signal?.addEventListener("abort", abortHandler, { once: true });
46
+ });
21
47
  }
22
48
  //#endregion
23
49
  exports.timeout = timeout;
@@ -1,11 +1,18 @@
1
1
  import { TimeoutError } from "../error/TimeoutError.mjs";
2
- import { delay } from "./delay.mjs";
3
2
  //#region src/promise/timeout.ts
4
3
  /**
5
4
  * Returns a promise that rejects with a `TimeoutError` after a specified delay.
6
5
  *
6
+ * You can pass an `AbortSignal` to cancel the timeout. Unlike most `AbortSignal`-aware
7
+ * APIs, aborting does **not** reject the promise. A `timeout` only exists to lose a
8
+ * `Promise.race`, so cancelling it leaves the promise pending forever, allowing the
9
+ * operation it guards to settle on its own. The underlying timer and abort listener
10
+ * are cleared on abort, so nothing is leaked.
11
+ *
7
12
  * @param ms - The delay duration in milliseconds.
8
- * @returns A promise that rejects with a `TimeoutError` after the specified delay.
13
+ * @param options - The options object.
14
+ * @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the returned promise never settles.
15
+ * @returns A promise that rejects with a `TimeoutError` after the specified delay, or never settles if aborted.
9
16
  * @throws {TimeoutError} Throws a `TimeoutError` after the specified delay.
10
17
  *
11
18
  * @example
@@ -14,10 +21,29 @@ import { delay } from "./delay.mjs";
14
21
  * } catch (error) {
15
22
  * console.error(error); // Will log 'The operation was timed out'
16
23
  * }
24
+ *
25
+ * @example
26
+ * // Cancelling the timeout lifts the time limit instead of throwing.
27
+ * const controller = new AbortController();
28
+ * setTimeout(() => controller.abort(), 50);
29
+ *
30
+ * const result = await Promise.race([
31
+ * doWork(),
32
+ * timeout(1000, { signal: controller.signal }), // never rejects once aborted
33
+ * ]);
17
34
  */
18
- async function timeout(ms) {
19
- await delay(ms);
20
- throw new TimeoutError();
35
+ function timeout(ms, { signal } = {}) {
36
+ return new Promise((_resolve, reject) => {
37
+ const abortHandler = () => {
38
+ clearTimeout(timeoutId);
39
+ };
40
+ if (signal?.aborted) return;
41
+ const timeoutId = setTimeout(() => {
42
+ signal?.removeEventListener("abort", abortHandler);
43
+ reject(new TimeoutError());
44
+ }, ms);
45
+ signal?.addEventListener("abort", abortHandler, { once: true });
46
+ });
21
47
  }
22
48
  //#endregion
23
49
  export { timeout };
@@ -1,14 +1,23 @@
1
1
  //#region src/promise/withTimeout.d.ts
2
+ interface WithTimeoutOptions {
3
+ signal?: AbortSignal;
4
+ }
2
5
  /**
3
6
  * Executes an async function and enforces a timeout.
4
7
  *
5
8
  * If the promise does not resolve within the specified time,
6
9
  * the timeout will trigger and the returned promise will be rejected.
7
10
  *
11
+ * You can pass an `AbortSignal` to cancel the timeout. Aborting the signal lifts the
12
+ * time limit: the timeout stops counting and `run`'s promise is awaited without a
13
+ * deadline. It does not reject the returned promise or abort `run` itself — pass the
14
+ * same signal into `run` if you also want to cancel the underlying work.
8
15
  *
9
16
  * @template T
10
17
  * @param run - A function that returns a promise to be executed.
11
18
  * @param ms - The timeout duration in milliseconds.
19
+ * @param options - The options object.
20
+ * @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the time limit is lifted.
12
21
  * @returns A promise that resolves with the result of the `run` function or rejects if the timeout is reached.
13
22
  *
14
23
  * @example
@@ -23,7 +32,16 @@
23
32
  * } catch (error) {
24
33
  * console.error(error); // Will log 'TimeoutError' if `fetchData` is not resolved within 1 second.
25
34
  * }
35
+ *
36
+ * @example
37
+ * // Lift the time limit when the user opts to keep waiting.
38
+ * const controller = new AbortController();
39
+ * keepWaitingButton.onclick = () => controller.abort();
40
+ *
41
+ * const data = await withTimeout(fetchData, 1000, { signal: controller.signal });
26
42
  */
27
- declare function withTimeout<T>(run: () => Promise<T>, ms: number): Promise<T>;
43
+ declare function withTimeout<T>(run: () => Promise<T>, ms: number, {
44
+ signal
45
+ }?: WithTimeoutOptions): Promise<T>;
28
46
  //#endregion
29
47
  export { withTimeout };
@@ -1,14 +1,23 @@
1
1
  //#region src/promise/withTimeout.d.ts
2
+ interface WithTimeoutOptions {
3
+ signal?: AbortSignal;
4
+ }
2
5
  /**
3
6
  * Executes an async function and enforces a timeout.
4
7
  *
5
8
  * If the promise does not resolve within the specified time,
6
9
  * the timeout will trigger and the returned promise will be rejected.
7
10
  *
11
+ * You can pass an `AbortSignal` to cancel the timeout. Aborting the signal lifts the
12
+ * time limit: the timeout stops counting and `run`'s promise is awaited without a
13
+ * deadline. It does not reject the returned promise or abort `run` itself — pass the
14
+ * same signal into `run` if you also want to cancel the underlying work.
8
15
  *
9
16
  * @template T
10
17
  * @param run - A function that returns a promise to be executed.
11
18
  * @param ms - The timeout duration in milliseconds.
19
+ * @param options - The options object.
20
+ * @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the time limit is lifted.
12
21
  * @returns A promise that resolves with the result of the `run` function or rejects if the timeout is reached.
13
22
  *
14
23
  * @example
@@ -23,7 +32,16 @@
23
32
  * } catch (error) {
24
33
  * console.error(error); // Will log 'TimeoutError' if `fetchData` is not resolved within 1 second.
25
34
  * }
35
+ *
36
+ * @example
37
+ * // Lift the time limit when the user opts to keep waiting.
38
+ * const controller = new AbortController();
39
+ * keepWaitingButton.onclick = () => controller.abort();
40
+ *
41
+ * const data = await withTimeout(fetchData, 1000, { signal: controller.signal });
26
42
  */
27
- declare function withTimeout<T>(run: () => Promise<T>, ms: number): Promise<T>;
43
+ declare function withTimeout<T>(run: () => Promise<T>, ms: number, {
44
+ signal
45
+ }?: WithTimeoutOptions): Promise<T>;
28
46
  //#endregion
29
47
  export { withTimeout };
@@ -6,10 +6,16 @@ const require_timeout = require("./timeout.js");
6
6
  * If the promise does not resolve within the specified time,
7
7
  * the timeout will trigger and the returned promise will be rejected.
8
8
  *
9
+ * You can pass an `AbortSignal` to cancel the timeout. Aborting the signal lifts the
10
+ * time limit: the timeout stops counting and `run`'s promise is awaited without a
11
+ * deadline. It does not reject the returned promise or abort `run` itself — pass the
12
+ * same signal into `run` if you also want to cancel the underlying work.
9
13
  *
10
14
  * @template T
11
15
  * @param run - A function that returns a promise to be executed.
12
16
  * @param ms - The timeout duration in milliseconds.
17
+ * @param options - The options object.
18
+ * @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the time limit is lifted.
13
19
  * @returns A promise that resolves with the result of the `run` function or rejects if the timeout is reached.
14
20
  *
15
21
  * @example
@@ -24,9 +30,16 @@ const require_timeout = require("./timeout.js");
24
30
  * } catch (error) {
25
31
  * console.error(error); // Will log 'TimeoutError' if `fetchData` is not resolved within 1 second.
26
32
  * }
33
+ *
34
+ * @example
35
+ * // Lift the time limit when the user opts to keep waiting.
36
+ * const controller = new AbortController();
37
+ * keepWaitingButton.onclick = () => controller.abort();
38
+ *
39
+ * const data = await withTimeout(fetchData, 1000, { signal: controller.signal });
27
40
  */
28
- async function withTimeout(run, ms) {
29
- return Promise.race([run(), require_timeout.timeout(ms)]);
41
+ async function withTimeout(run, ms, { signal } = {}) {
42
+ return Promise.race([run(), require_timeout.timeout(ms, { signal })]);
30
43
  }
31
44
  //#endregion
32
45
  exports.withTimeout = withTimeout;
@@ -6,10 +6,16 @@ import { timeout } from "./timeout.mjs";
6
6
  * If the promise does not resolve within the specified time,
7
7
  * the timeout will trigger and the returned promise will be rejected.
8
8
  *
9
+ * You can pass an `AbortSignal` to cancel the timeout. Aborting the signal lifts the
10
+ * time limit: the timeout stops counting and `run`'s promise is awaited without a
11
+ * deadline. It does not reject the returned promise or abort `run` itself — pass the
12
+ * same signal into `run` if you also want to cancel the underlying work.
9
13
  *
10
14
  * @template T
11
15
  * @param run - A function that returns a promise to be executed.
12
16
  * @param ms - The timeout duration in milliseconds.
17
+ * @param options - The options object.
18
+ * @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the time limit is lifted.
13
19
  * @returns A promise that resolves with the result of the `run` function or rejects if the timeout is reached.
14
20
  *
15
21
  * @example
@@ -24,9 +30,16 @@ import { timeout } from "./timeout.mjs";
24
30
  * } catch (error) {
25
31
  * console.error(error); // Will log 'TimeoutError' if `fetchData` is not resolved within 1 second.
26
32
  * }
33
+ *
34
+ * @example
35
+ * // Lift the time limit when the user opts to keep waiting.
36
+ * const controller = new AbortController();
37
+ * keepWaitingButton.onclick = () => controller.abort();
38
+ *
39
+ * const data = await withTimeout(fetchData, 1000, { signal: controller.signal });
27
40
  */
28
- async function withTimeout(run, ms) {
29
- return Promise.race([run(), timeout(ms)]);
41
+ async function withTimeout(run, ms, { signal } = {}) {
42
+ return Promise.race([run(), timeout(ms, { signal })]);
30
43
  }
31
44
  //#endregion
32
45
  export { withTimeout };