@superutils/promise 1.1.5 → 1.2.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  An extended `Promise` implementation, named `PromisE`, that provides additional features and utilities for easier asynchronous flow control in JavaScript and TypeScript applications.
4
4
 
5
- This package offers a drop-in replacement for the native `Promise` that includes status tracking (`.pending`, `.resolved`, `.rejected`) and a suite of practical static methods for common asynchronous patterns like deferred execution, throttling, and cancellable fetches.
5
+ This package offers a drop-in replacement for the native `Promise` that includes status tracking (`.pending`, `.resolved`, `.rejected`) and a suite of practical static methods for common asynchronous patterns like deferred execution, throttling, auto-retry and timeout.
6
6
 
7
7
  <div v-if="false">
8
8
 
@@ -16,39 +16,52 @@ For full API reference check out the [docs page](https://alien45.github.io/super
16
16
  - [Installation](#installation)
17
17
  - [Usage](#usage)
18
18
  - [`new PromisE(executor)`](#promise-executor): Drop-in replacement for `Promise`
19
- - [`new PromisE(promise)`](#promise-status): Check promise status
19
+ - [Status tracking](#status-tracking)
20
+ - [Early Finalization](#early-finalization)
21
+ - [`new PromisE(promise)`](#promise-status): Check status of an existing promise.
20
22
  - [`PromisE.try()`](#static-methods): Static methods
21
23
  - [`PromisE.delay()`](#delay): Async delay
22
24
  - [`PromisE.deferred()`](#deferred): Async debounced/throttled callback
25
+ - [Debounce Example](#debounce-example)
26
+ - [Throttle Example](#throttle-example)
27
+ - [Behavior with different `options`](#behavior-with-different-options)
23
28
  - [`PromisE.timeout()`](#timeout): Reject after timeout
24
29
 
25
30
  ## Features
26
31
 
27
32
  - **Promise Status**: Easily check if a promise is `pending`, `resolved`, or `rejected`.
28
33
  - **Deferred Execution**: Defer or throttle promise-based function calls with `PromisE.deferred()`.
29
- - **Auto-cancellable Fetch**: Automatically abort pending requests when subsequent requests are made using `PromisE.deferredFetch()` and `PromisE.deferredPost()`.
30
- - **Auto-cancellable Fetch**: The `PromisE.deferredFetch` and `PromisE.deferredPost` utilities automatically abort pending requests when a new deferred/throttled call is made.
31
34
  - **Timeouts**: Wrap any promise with a timeout using `PromisE.timeout()`.
32
35
  - **Rich Utilities**: A collection of static methods like `.all()`, `.race()`, `.delay()`, and more, all returning `PromisE` instances.
33
36
 
34
37
  ## Installation
35
38
 
36
39
  ```bash
37
- npm install @superutils/core @superutils/promise
40
+ npm install @superutils/promise
38
41
  ```
39
42
 
43
+ Dependency: `@superutils/core` will be automatically installed by NPM
44
+
40
45
  ## Usage
41
46
 
42
47
  <div id="promise-executor"></div>
43
48
 
44
49
  ### `new PromisE(executor)`: Drop-in replacement for `Promise`
45
50
 
46
- The `PromisE` class can be used just like the native `Promise`. The key difference is the addition of status properties:
51
+ The `PromisE` class is an extension of the built-in `Promise` class and can be used as a drop-in replacement. It is fully compatible with async/await and `Promise` static methods.
47
52
 
48
- ```typescript
49
- import { PromisE } from '@superutils/promise'
53
+ A `PromisE` instance has the following additional features in comparison to `Promise`:
54
+
55
+ #### Status tracking:
56
+
57
+ All instances come with `.pending`, `.resolved` and `.rejected` read-only properties that indicate the current state of the promise.
50
58
 
51
- const p = new PromisE(resolve => setTimeout(() => resolve('done'), 1000))
59
+ ```javascript
60
+ import Promise from '@superutils/promise'
61
+
62
+ // Importing `PromisE` as "Promise" allows it to be used as a drop-in replacement without changing existing code
63
+
64
+ const p = new Promise(resolve => setTimeout(() => resolve('done'), 1000))
52
65
 
53
66
  console.log(p.pending) // true
54
67
 
@@ -59,24 +72,33 @@ p.then(result => {
59
72
  })
60
73
  ```
61
74
 
62
- and the ability to early finalize a promise:
75
+ #### Early finalization:
76
+
77
+ All `PromisE` instances expose `.resolve()` and `.reject()` methods that allow early finalization and `.onEarlyFinalize` array that allows adding callbacks to be executed when the promise is finalized externally using these methods.
78
+
79
+ ```javascript
80
+ import PromisE from '@superutils/promise'
63
81
 
64
- ```typescript
65
- import { PromisE } from '@superutils/promise'
66
82
  const p = new PromisE(resolve => setTimeout(() => resolve('done'), 10000))
67
83
  p.then(result => console.log(result))
68
84
  // resolve the promise early
69
85
  setTimeout(() => p.resolve('finished early'), 500)
86
+
87
+ // Add a callback to do stuff whenever promise is finalized externally.
88
+ // This will not be invoked if promise finalized naturally using the Promise executor.
89
+ p.onEarlyFinalize.push(((resolved, valueOrReason) =>
90
+ console.log('Promise finalized externally:', { resolved, valueOrReason }),
91
+ ))
70
92
  ```
71
93
 
72
94
  <div id="static-methods"></div>
73
95
 
74
- ### `PromisE.try(fn)`: Static methods
96
+ ### Static methods
75
97
 
76
98
  Drop-in replacement for all `Promise` static methods such as `.all()`, `.race()`, `.reject`, `.resolve`, `.try()`, `.withResolvers()`....
77
99
 
78
- ```typescript
79
- import { PromisE } from '@superutils/promise'
100
+ ```javascript
101
+ import PromisE from '@superutils/promise'
80
102
 
81
103
  const p = PromisE.try(() => {
82
104
  throw new Error('Something went wrong')
@@ -90,12 +112,11 @@ p.catch(error => {
90
112
 
91
113
  <div id="promise-status"></div>
92
114
 
93
- ### `new PromisE(promise)`
115
+ ### `new PromisE(promise)`: Check status of an existing promise.
94
116
 
95
- Check status of an existing promise.
117
+ ```javascript
118
+ import PromisE from '@superutils/promise'
96
119
 
97
- ```typescript
98
- import { PromisE } from '@superutils/promise'
99
120
  const x = Promise.resolve(1)
100
121
  const p = new PromisE(x)
101
122
  console.log(p.pending) // false
@@ -109,8 +130,9 @@ console.log(p.rejected) // false
109
130
 
110
131
  Creates a promise that resolves after a specified duration, essentially a promise-based `setTimeout`.
111
132
 
112
- ```typescript
133
+ ```javascript
113
134
  import PromisE from '@superutils/promise'
135
+
114
136
  // Wait until `appReady` becomes truthy but
115
137
  while (!appReady) {
116
138
  await PromisE.delay(100)
@@ -123,16 +145,13 @@ Creates a promise that executes a function after a specified duration and return
123
145
 
124
146
  If callback returns undefined, default value will be the duration.
125
147
 
126
- ```typescript
148
+ ```javascript
127
149
  import PromisE from '@superutils/promise'
128
150
 
129
- const func = async () => {
130
- console.log('Waiting for app initialization or something else to be ready')
131
- // wait 3 seconds before proceeding
132
- await PromisE.delay(3000)
133
- console.log('App ready')
134
- }
135
- func()
151
+ console.log('Waiting for app initialization or something else to be ready')
152
+ const onReady = () => console.log('App ready')
153
+
154
+ PromisE.delay(3000, onReady)
136
155
  ```
137
156
 
138
157
  <div id="deferred"></div>
@@ -144,6 +163,8 @@ Create a function that debounces or throttles promise-returning function calls.
144
163
  #### Debounce example:
145
164
 
146
165
  ```typescript
166
+ import PromisE from '@superutils/promise'
167
+
147
168
  const example = async (options = {}) => {
148
169
  const df = PromisE.deferred({
149
170
  delayMs: 100,
@@ -165,7 +186,10 @@ example({ ignoreStale: true, throttle: false })
165
186
 
166
187
  #### Throttle example:
167
188
 
168
- ```typescript
189
+ ```javascript
190
+ import PromisE from '@superutils/promise'
191
+
192
+ // Simulate an example scenario
169
193
  const example = async (options = {}) => {
170
194
  const df = PromisE.deferred({
171
195
  delayMs: 100,
@@ -185,6 +209,22 @@ example({ ignoreStale: false, throttle: true })
185
209
  // `200` and `5000` will be printed in the console
186
210
  ```
187
211
 
212
+ #### Behavior with different `options`:
213
+
214
+ - **`delayMs: PositiveNumber, throttle: false`**: (default) Debounce mode.
215
+ - **`throttle: true`**: Switches from debounce to throttle mode.
216
+ - **`delayMs: 0`**: Disables debouncing and throttling, enabling sequential/queue mode. Requests are executed one after the other. Any failed promise does not affect subsequent promises.
217
+ - **`resolveIgnored` (enum)**: Controls how an ignored promises is handled.
218
+ 1. `ResolveIgnored.WITH_UNDEFINED`: The promise for the ignored request resolves with `undefined`.
219
+ 2. `ResolveIgnored.WITH_LAST`: The promise for the ignored request waits (if needed) and resolves with the last/most-recent finalized promise.
220
+ 3. `ResolveIgnored.NEVER`: The promise for the ignored request is neither resolved nor rejected. It will remain pending indefinitely.
221
+ > **Warning:** Use with caution, as this may lead to memory leaks if not handled properly.
222
+ - **`resolveError` (enum)**: Controls how failed requests are handled.
223
+ 1. `ResolveError.NEVER`: The promise for a failed request will neither resolve nor reject, causing it to remain pending indefinitely.
224
+ > **Warning:** Use with caution, as this may lead to memory leaks if not handled properly.
225
+ 2. `ResolveError.WITH_ERROR`: The promise resolves with the error object instead of being rejected.
226
+ 3. `ResolveError.WITH_UNDEFINED`: The promise resolves with an `undefined` value upon failure.
227
+
188
228
  <div id="deferredCallback"></div>
189
229
 
190
230
  ### `PromisE.deferredCallback(callback, options)`: async debounced/throttled callbacks
@@ -221,7 +261,7 @@ delays.forEach(timeout =>
221
261
  #### Reject stuck or unexpectedly lenghthy promise(s) after a specified timeout:
222
262
 
223
263
  ```typescript
224
- import { PromisE } from '@superutils/promise'
264
+ import PromisE from '@superutils/promise'
225
265
 
226
266
  PromisE.timeout(
227
267
  5000, // timeout after 5000ms
@@ -233,13 +273,13 @@ PromisE.timeout(
233
273
  #### Show a message when loading is too long:
234
274
 
235
275
  ```typescript
236
- import { PromisE } from '@superutils/promise'
276
+ import PromisE from '@superutils/promise'
237
277
 
238
- const loadUserNProducts = () => {
278
+ const loadUserNProducts = async () => {
239
279
  const promise = PromisE.timeout(
240
280
  5000, // timeout after 5000ms
241
- api.getUser(),
242
- api.getProducts(),
281
+ fetch('https://dummyjson.com/users/1'), // fetch user
282
+ fetch('https://dummyjson.com/products'), // fetch products
243
283
  )
244
284
  const [user, products] = await promise.catch(err => {
245
285
  // promise did not time out, but was rejected
@@ -254,5 +294,5 @@ const loadUserNProducts = () => {
254
294
  })
255
295
  return [user, products]
256
296
  }
257
- loadUserNProducts()
297
+ loadUserNProducts().catch(console.warn)
258
298
  ```
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _superutils_core from '@superutils/core';
2
- import { ValueOrPromise, TimeoutId, PositiveNumber, ThrottleOptions, DeferredOptions } from '@superutils/core';
2
+ import { ValueOrPromise, PositiveNumber, DeferredOptions, TimeoutId } from '@superutils/core';
3
3
 
4
4
  interface IPromisE<T = unknown> extends Promise<T> {
5
5
  /** 0: pending, 1: resolved, 2: rejected */
@@ -17,73 +17,18 @@ interface IPromisE<T = unknown> extends Promise<T> {
17
17
  /** Indicates if the promise has been resolved */
18
18
  readonly resolved: boolean;
19
19
  }
20
- interface IPromisE_Delay<T = unknown> extends Promise<T>, IPromisE<T> {
21
- /**
22
- * Caution: pausing will prevent the promise from resolving/rejeting automatically.
23
- *
24
- * In order to finalize the promise either the `resolve()` or the `reject()` method must be invoked manually.
25
- *
26
- * An never-finalized promise may cause memory leak and will leave it at the mercry of the garbage collector.
27
- * Use `pause()` only if you are sure.
28
- *
29
- * @example
30
- * ```typescript
31
- * // Example 1: SAFE => no memory leak, because no reference to the promise is stored and no suspended code
32
- * <button onClick={() => {
33
- * const promise = PromisE.delay(1000).then(... do stuff ....)
34
- * setTimeout(() => promise.pause(), 300)
35
- * }}>Click Me</button>
36
- * ```
37
- *
38
- * @example UNSAFE => potential memory leak, because of suspended code
39
- * ```typescript
40
- * <button onClick={() => {
41
- * const promise = PromisE.delay(1000)
42
- * setTimeout(() => promise.pause(), 300)
43
- * await promise // suspended code
44
- * //... do stuff ....
45
- * }}>Click Me</button>
46
- * ```
47
- *
48
- * @example UNSAFE => potential memory leak, because of preserved reference.
49
- * ```typescript
50
- * // Until the reference to promises is collected by the garbage collector,
51
- * // reference to the unfinished promise will remain in memory.
52
- * const promises = []
53
- * <button onClick={() => {
54
- * const promise = PromisE.delay(1000)
55
- * setTimeout(() => promise.pause(), 300)
56
- * promises.push(promise)
57
- * }}>Click Me</button>
58
- * ```
59
- */
60
- pause: () => void;
61
- /** Timeout ID */
62
- timeoutId: TimeoutId;
63
- }
64
- /**
65
- * Descibes a timeout PromisE and it's additional properties.
66
- */
67
- type IPromisE_Timeout<T = unknown> = IPromisE<T> & {
68
- /** Clearing the timeout will prevent it from timing out */
69
- clearTimeout: () => void;
70
- /** The result/data promise. If more than one supplied in `args` result promise will be a combined `PromisE.all` */
71
- data: IPromisE<T>;
72
- /** A shorthand getter to check if the promise has timed out. Same as `promise.timeout.rejected`. */
73
- readonly timedout: boolean;
74
- /** The timeout promise */
75
- timeout: IPromisE_Delay<T>;
76
- };
77
20
  type OnEarlyFinalize<T> = <TResolved extends boolean, TValue = TResolved extends true ? T : unknown>(resolved: TResolved, resultOrReason: TValue) => ValueOrPromise<unknown>;
78
21
  type PromiseParams<T = unknown> = ConstructorParameters<typeof Promise<T>>;
79
22
 
80
23
  /** Return type of `PromisE.deferred()` */
81
24
  type DeferredAsyncCallback<TArgs extends unknown[] | [] = []> = <TResult = unknown>(promise: Promise<TResult> | ((...args: TArgs) => Promise<TResult>)) => IPromisE<TResult>;
82
- type DeferredAsyncGetPromise<T> = <TResult = T>() => Promise<TResult>;
25
+ type GetPromiseFunc<T> = <TResult = T>() => Promise<TResult>;
83
26
  /** Default options used by `PromisE.deferred` and related functions */
84
- type DeferredAsyncDefaults<ThisArg = unknown, Delay = unknown> = Pick<Required<DeferredAsyncOptions<ThisArg, Delay>>, 'delayMs' | 'resolveError' | 'resolveIgnored'>;
27
+ type DeferredAsyncDefaults<ThisArg = unknown, Delay = unknown> = Pick<Required<DeferredAsyncOptions<ThisArg, Delay>>, 'resolveError' | 'resolveIgnored'> & {
28
+ delayMs: number;
29
+ };
85
30
  /** Options for `PromisE.deferred` and other related functions */
86
- type DeferredAsyncOptions<ThisArg = unknown, Delay = unknown> = {
31
+ type DeferredAsyncOptions<ThisArg = unknown, Delay = number> = {
87
32
  /**
88
33
  * Delay in milliseconds, used for `debounce` and `throttle` modes. Use `0` for sequential execution.
89
34
  *
@@ -91,7 +36,7 @@ type DeferredAsyncOptions<ThisArg = unknown, Delay = unknown> = {
91
36
  *
92
37
  * Default: `100` (or whatever is set in `PromisE.deferred.defaults.delayMs`)
93
38
  */
94
- delayMs?: 0 | PositiveNumber<Delay>;
39
+ delayMs?: number | PositiveNumber<Delay>;
95
40
  /**
96
41
  * Whether to ignore (based on `resolveIgnored` settings) stale promises.
97
42
  * In debouce/throttle mode, when an older promise is resolved after a newly resolved promise,
@@ -100,14 +45,12 @@ type DeferredAsyncOptions<ThisArg = unknown, Delay = unknown> = {
100
45
  * Default: `false`
101
46
  */
102
47
  ignoreStale?: boolean;
103
- /** Callback invoked whenever promise/function throws error */
104
- onError?: (this: ThisArg, err: unknown) => ValueOrPromise<unknown>;
105
48
  /**
106
49
  * Whenever a promise/function is ignored when in debounce/throttle mode, `onIgnored` wil be invoked.
107
50
  * The promise/function will not be invoked, unless it's manually invoked using the `ignored` function.
108
51
  * Use for debugging or logging purposes.
109
52
  */
110
- onIgnore?: (this: ThisArg, ignored: DeferredAsyncGetPromise<unknown>) => ValueOrPromise<unknown>;
53
+ onIgnore?: (this: ThisArg, ignored: GetPromiseFunc<unknown>) => ValueOrPromise<unknown>;
111
54
  /**
112
55
  * Whenever a promise/function is executed successfully `onResult` will be called.
113
56
  * Those that are ignored but resolve with last will not cause `onResult` to be invoked.
@@ -125,18 +68,17 @@ type DeferredAsyncOptions<ThisArg = unknown, Delay = unknown> = {
125
68
  * See {@link ResolveError} for available options.
126
69
  */
127
70
  resolveError?: ResolveError;
128
- /** The value to be used as "thisArg" whenever any of the callbacks are invoked */
129
- thisArg?: ThisArg;
130
71
  } & (({
131
- delayMs: PositiveNumber<Delay>;
132
- throttle: true;
133
- } & Pick<ThrottleOptions, 'trailing'>) | ({
134
72
  delayMs?: PositiveNumber<Delay>;
135
- throttle?: false;
136
- } & Pick<DeferredOptions, 'leading'>) | {
73
+ } & DeferredOptions<ThisArg>) | ({
137
74
  delayMs: 0;
75
+ } & {
138
76
  throttle?: false;
139
- });
77
+ /** Callback invoked whenever promise/function throws error */
78
+ onError?: (this: ThisArg, err: unknown) => ValueOrPromise<unknown>;
79
+ /** The value to be used as "thisArg" whenever any of the callbacks are invoked */
80
+ thisArg?: ThisArg;
81
+ }));
140
82
  /** Determines what to do when deferred promise/function fails */
141
83
  declare enum ResolveError {
142
84
  /** Neither resolve nor reject the failed */
@@ -161,6 +103,53 @@ declare enum ResolveIgnored {
161
103
  WITH_UNDEFINED = "WITH_UNDEFINED"
162
104
  }
163
105
 
106
+ interface IPromisE_Delay<T = unknown> extends Promise<T>, IPromisE<T> {
107
+ /**
108
+ * Caution: pausing will prevent the promise from resolving/rejeting automatically.
109
+ *
110
+ * In order to finalize the promise either the `resolve()` or the `reject()` method must be invoked manually.
111
+ *
112
+ * An never-finalized promise may cause memory leak and will leave it at the mercry of the garbage collector.
113
+ * Use `pause()` only if you are sure.
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * // Example 1: SAFE => no memory leak, because no reference to the promise is stored and no suspended code
118
+ * <button onClick={() => {
119
+ * const promise = PromisE.delay(1000).then(... do stuff ....)
120
+ * setTimeout(() => promise.pause(), 300)
121
+ * }}>Click Me</button>
122
+ * ```
123
+ *
124
+ * @example UNSAFE => potential memory leak, because of suspended code
125
+ * ```typescript
126
+ * <button onClick={() => {
127
+ * const promise = PromisE.delay(1000)
128
+ * setTimeout(() => promise.pause(), 300)
129
+ * await promise // suspended code
130
+ * //... do stuff ....
131
+ * }}>Click Me</button>
132
+ * ```
133
+ *
134
+ * @example UNSAFE => potential memory leak, because of preserved reference.
135
+ * ```typescript
136
+ * // Until the reference to promises is collected by the garbage collector,
137
+ * // reference to the unfinished promise will remain in memory.
138
+ * const promises = []
139
+ * <button onClick={() => {
140
+ * const promise = PromisE.delay(1000)
141
+ * setTimeout(() => promise.pause(), 300)
142
+ * promises.push(promise)
143
+ * }}>Click Me</button>
144
+ * ```
145
+ */
146
+ pause: () => void;
147
+ /** Timeout ID */
148
+ timeoutId: TimeoutId;
149
+ }
150
+
151
+ /** Function to determine whether retry should be attempted based on previous result/error */
152
+ type RetryIfFunc<T = unknown> = (prevResult: T | undefined, retryCount: number, error?: unknown) => ValueOrPromise<boolean | void>;
164
153
  /** Options for automatic retry mechanism */
165
154
  type RetryOptions<T = unknown> = {
166
155
  /**
@@ -179,30 +168,47 @@ type RetryOptions<T = unknown> = {
179
168
  */
180
169
  retryBackOff?: 'exponential' | 'linear';
181
170
  /**
182
- * Delay in milliseconds between retries.
171
+ * Minimum delay in milliseconds between retries.
183
172
  * Default: `300`
184
173
  */
185
174
  retryDelay?: number;
186
175
  /**
187
- * Add a random delay between 0ms and `retryDelayJitterMax` to the `retryDelay`.
176
+ * Whether to add a random delay between 0ms and `retryDelayJitterMax` to the `retryDelay`.
177
+ *
178
+ * This helps to avoid "thundering herd" problem when multiple retries are attempted simultaneously.
179
+ *
188
180
  * Default: `true`
189
181
  */
190
182
  retryDelayJitter?: boolean;
191
183
  /**
192
- * Maximum delay (in milliseconds) to be used when randomly generating jitter delay duration.
184
+ * Maximum jitter delay (in milliseconds) to be added to the `retryDelay` when `retryDelayJitter` is `true`.
185
+ *
186
+ * A random value between `0` and `retryDelayJitterMax` will be added to the base `retryDelay`.
187
+ *
193
188
  * Default: `100`
194
189
  */
195
190
  retryDelayJitterMax?: number;
196
191
  /**
197
- * Additional condition/function to be used to determine whether function should be retried.
198
- * `retryIf` will only be executed when function execution is successful.
192
+ * Additional condition to be used to determine whether function should be retried.
193
+ *
194
+ * If `retryIf` not provided, execution will be retried on any error until the retry limit is reached
195
+ * or a result is received without an exception.
196
+ *
197
+ * @param prevResult The result from the previous attempt, if any.
198
+ * @param retryCount The number of retries that have been attempted so far.
199
+ * @param error The error from the previous attempt, if any.
200
+ *
201
+ * Expected return values:
202
+ * - `true`: retry will be attempted regardless of error.
203
+ * - `false`: no further retry will be attempted and the last error/result will be returned.
204
+ * - `void | undefined`: retry will be attempted only if there was an error.
199
205
  */
200
- retryIf?: null | ((prevResult: T | undefined, retryCount: number) => boolean | Promise<boolean>);
206
+ retryIf?: RetryIfFunc<T>;
201
207
  };
202
208
 
203
209
  declare class PromisEBase<T = unknown> extends Promise<T> implements IPromisE<T> {
204
- private _resolve?;
205
- private _reject?;
210
+ private _resolve;
211
+ private _reject;
206
212
  private _state;
207
213
  /**
208
214
  * callbacks to be invoked whenever PromisE instance is finalized early using non-static resolve()/reject() methods */
@@ -213,6 +219,8 @@ declare class PromisEBase<T = unknown> extends Promise<T> implements IPromisE<T>
213
219
  constructor(promise: Promise<T>);
214
220
  /** Create a resolved promise with value */
215
221
  constructor(value: T);
222
+ /** Create a promise to be resolved externally using `.resolve()` and `.reject()` methods */
223
+ constructor(value: undefined);
216
224
  /**
217
225
  * If executor function is not provided, the promise must be resolved/rejected externally.
218
226
  *
@@ -253,7 +261,7 @@ declare class PromisEBase<T = unknown> extends Promise<T> implements IPromisE<T>
253
261
  /** Sugar for `new PromisE(Promise.race(..))` */
254
262
  static race: <T_1 extends unknown[]>(values: T_1) => IPromisE<Awaited<T_1[number]>>;
255
263
  /** Extends Promise.reject */
256
- static reject: <T_1 = never>(reason: unknown) => IPromisE<T_1>;
264
+ static reject: <T_1 = never>(reason: unknown) => PromisEBase<T_1>;
257
265
  /** Sugar for `new PromisE(Promise.resolve(...))` */
258
266
  static resolve: <T_1>(value?: T_1 | PromiseLike<T_1>) => IPromisE<T_1>;
259
267
  /** Sugar for `new PromisE(Promise.try(...))` */
@@ -290,29 +298,67 @@ declare class PromisEBase<T = unknown> extends Promise<T> implements IPromisE<T>
290
298
  };
291
299
  }
292
300
 
293
- type TimeoutFunc<T extends unknown[]> = {
301
+ /**
302
+ * Descibes a timeout PromisE and it's additional properties.
303
+ */
304
+ type IPromisE_Timeout<T = unknown> = IPromisE<T> & {
305
+ readonly aborted: boolean;
306
+ /**
307
+ * Removes `abortCtrl/signal` listeners, effectively disabling external cancellation via AbortController.
308
+ */
309
+ cancelAbort: () => void;
310
+ /**
311
+ * Clears the timeout timer, preventing the promise from being rejected due to a timeout.
312
+ */
313
+ clearTimeout: () => void;
314
+ /** The underlying data promise. If multiple promises were passed to `timeout`, this represents the combined result (defaulting to `PromisE.all`). */
315
+ data: IPromisE<T>;
316
+ /** Read-only property indicating if the promise timed out. Equivalent to checking `promise.timeout.rejected`. */
317
+ readonly timedout: boolean;
318
+ /** The internal promise that handles the timeout logic. It rejects when the duration expires. */
319
+ timeout: IPromisE_Delay<T>;
320
+ };
321
+ type TimeoutResult<T extends unknown[], TFunc extends keyof TimeoutFunc<T>, Values extends unknown[] = {
322
+ -readonly [P in keyof T]: T[P] extends (...args: unknown[]) => infer ReturnType ? ReturnType : T[P];
323
+ }> = Awaited<T['length'] extends 1 ? Values[0] : ReturnType<TimeoutFunc<Values>[TFunc]>>;
324
+ type TimeoutFunc<T extends unknown[] = []> = {
294
325
  all: typeof PromisEBase.all<T>;
295
326
  allSettled: typeof PromisEBase.allSettled<T>;
296
327
  any: typeof PromisEBase.any<T>;
297
328
  race: typeof PromisEBase.race<T>;
298
329
  };
299
330
  /**
300
- * `PromisE.timeout` options
331
+ * Options for `PromisE.timeout()`
301
332
  *
302
- * @param func (optional) name of the supported `PromieBase` static method. Default: `"all"`
333
+ * @param func (optional) name of the supported `PromiEBase` static method to be used to resolve
334
+ * when more than one promise/function is provided. Default: `"all"`
303
335
  * @param timeout (optional) timeout duration in milliseconds. Default: `10_000` (10 seconds)
304
336
  * @param timeoutMsg (optional) timeout error message. Default: `"Timed out after 10000ms"`
305
337
  *
306
338
  */
307
- type TimeoutOptions<Func extends string = 'all'> = {
308
- func: Func;
339
+ type TimeoutOptions<T extends unknown[] = [], Func extends string = 'all'> = {
340
+ abortCtrl?: AbortController;
341
+ func?: T['length'] extends 0 ? never : T['length'] extends 1 ? never : Func;
342
+ /**
343
+ * Callback invoked when the promise is rejected due to an abort signal.
344
+ * Optionally, return an `Error` object to reject the promise with a custom error.
345
+ */
346
+ onAbort?: () => ValueOrPromise<void | Error>;
347
+ /**
348
+ * Callback invoked when the promise times out.
349
+ * Optionally, return an `Error` object to reject the promise with a custom error.
350
+ */
351
+ onTimeout?: () => ValueOrPromise<void | Error>;
352
+ signal?: AbortSignal;
309
353
  timeout?: number;
310
- timeoutMsg?: string;
311
354
  };
355
+ /** Default options for `PromisE.timeout()` */
356
+ type TimeoutOptionsDefault = Required<Omit<TimeoutOptions<unknown[], keyof TimeoutFunc>, 'abortCtrl' | 'signal'>>;
312
357
 
313
358
  /**
314
359
  * @function PromisE.deferred
315
- * The adaptation of the `deferred()` function tailored for Promises.
360
+ *
361
+ * The adaptation of the `deferred()` function from `@superutils/core` tailored for Promises.
316
362
  *
317
363
  *
318
364
  * # Notes
@@ -500,7 +546,7 @@ declare function deferredCallback<TDefault = unknown, ThisArg = unknown, Delay =
500
546
  * func()
501
547
  * ```
502
548
  */
503
- declare function delay<T = number, TReject extends boolean = boolean>(duration?: number, result?: T | (() => T), asRejected?: TReject): IPromisE_Delay<T>;
549
+ declare function delay<T = number, TReject extends boolean = boolean>(duration?: number, result?: T | (() => T | Promise<T>), asRejected?: TReject): IPromisE_Delay<T>;
504
550
  declare namespace delay {
505
551
  var defaults: {
506
552
  /** Default delay duration in milliseconds */
@@ -544,18 +590,22 @@ declare namespace delay {
544
590
  */
545
591
  declare function delayReject<T = never>(duration: number, reason?: unknown): IPromisE_Delay<T>;
546
592
 
593
+ /** Timeout duration (in milliseconds) used as a fallback when positive number is not provided to {@link timeout} */
594
+ declare const FALLBACK_TIMEOUT = 10000;
547
595
  /**
548
596
  * Creates a new promise that wraps one or more promises and rejects if they do not settle within a
549
597
  * specified timeout duration. When multiple promises are provided, they can be processed using methods like
550
598
  * `all` (default), `race`, `any`, or `allSettled`.
551
599
  *
552
600
  * @param timeout (optional) timeout duration in milliseconds.
553
- * Default: `10000` (10 seconds)
601
+ * Default: `10_000` (10 seconds)
554
602
  *
555
603
  * @param values rest param containing one or more promises/values
556
604
  *
557
- * @example Wokring with a single promise: resolved succesfully
605
+ * @example Working with a single promise
558
606
  * ```typescript
607
+ * import PromisE from '@supertuils/promise'
608
+ *
559
609
  * PromisE.timeout(
560
610
  * 5000, // timeout after 5000ms
561
611
  * PromisE.delay(1000), // resolves after 1000ms with value 1000
@@ -563,8 +613,21 @@ declare function delayReject<T = never>(duration: number, reason?: unknown): IPr
563
613
  * // Result: 1000
564
614
  * ```
565
615
  *
616
+ * @example Working with a single function
617
+ * ```typescript
618
+ * import PromisE from '@supertuils/promise'
619
+ *
620
+ * PromisE.timeout(
621
+ * 5000, // timeout after 5000ms
622
+ * () => PromisE.delay(1000), // function resolves after 1000ms with value 1000
623
+ * ).then(console.log)
624
+ * // Result: 1000
625
+ * ```
626
+ *
566
627
  * @example Promise times out & rejected
567
628
  * ```typescript
629
+ * import PromisE from '@supertuils/promise'
630
+ *
568
631
  * PromisE.timeout(
569
632
  * 5000, // timeout after 5000ms
570
633
  * PromisE.delay(20000), // resolves after 20000ms with value 20000
@@ -572,13 +635,15 @@ declare function delayReject<T = never>(duration: number, reason?: unknown): IPr
572
635
  * // Error: Error('Timed out after 5000ms')
573
636
  *```
574
637
  *
575
- * @example Working with multiple promises, resolved using "PromisE.all()"
638
+ * @example Working with multiple promises/functions, resolved using "PromisE.all()"
576
639
  *
577
640
  * ```typescript
641
+ * import PromisE from '@supertuils/promise'
642
+ *
578
643
  * PromisE.timeout(
579
644
  * 5000, // timeout after 5000ms
580
645
  * PromisE.delay(1000), // resolves after 1000ms with value 1000
581
- * PromisE.delay(2000), // resolves after 2000ms with value 2000
646
+ * () => PromisE.delay(2000), // resolves after 2000ms with value 2000
582
647
  * PromisE.delay(3000), // resolves after 3000ms with value 3000
583
648
  * ).then(console.log)
584
649
  * // Result: [ 1000, 2000, 3000 ]
@@ -587,6 +652,8 @@ declare function delayReject<T = never>(duration: number, reason?: unknown): IPr
587
652
  * @example Promise times out & but not rejected.
588
653
  * Eg: when API request is taking longer than expected, print a message avoid rejecting the promise.
589
654
  * ```typescript
655
+ * import PromisE from '@supertuils/promise'
656
+ *
590
657
  * const promise = PromisE.timeout(
591
658
  * 5000, // timeout after 5000ms
592
659
  * PromisE.delay(20000), // data promise, resolves after 20000ms with value 20000
@@ -601,10 +668,32 @@ declare function delayReject<T = never>(duration: number, reason?: unknown): IPr
601
668
  * return promise.data
602
669
  * })
603
670
  *```
671
+ */
672
+ declare function timeout<T extends [unknown, ...unknown[]]>(timeout: number, ...values: T): IPromisE_Timeout<TimeoutResult<T, 'all'>>;
673
+ /**
674
+ *
675
+ * @param options An options object can be passed with one or more of the following properties:
676
+ * @param options.abortCtrl (optional) AbortController to manually reject promise externally and/or to sync abort with timeout rejection
677
+ * @param options.abortMsg (optional) error message when promise is rejected by abort controller/signal
678
+ * @param options.func (optional) Name of the `PromisE` static method to be used to combine the `values`.
679
+ * Only used when more than one promise is provided. Default: `"all"`
680
+ *
681
+ * Accepted values:
682
+ * 1. `'all'` **(default)**: for `PromisE.all`
683
+ * 2. `'allSettled'`: for `PromisE.allSettled`
684
+ * 3. `'any'`: for `PromisE.any`
685
+ * 4. `'race'`: for `PromisE.race`
686
+ * @param options.signal (optional) AbortSignal to manually reject promise externally
687
+ * @param options.timeout (optional) timeout duration in milliseconds. If positive number is not provided, the default value will be used. Default: `10_000` (10 seconds)
688
+ * @param options.timeoutMsg (optional) custom error message to be used when promises timeout.
604
689
  *
605
- * @example Multiple promises resolved using "PromisE.race()"
690
+ * @param values Mix of promises, values and/or functions
691
+ *
692
+ * @example Working with multiple promises/functions resolved using "PromisE.race()"
606
693
  *
607
694
  * ```typescript
695
+ * import PromisE from '@supertuils/promise'
696
+ *
608
697
  * PromisE.timeout(
609
698
  * { // instead of `timeout: number` an object can be used for additional options
610
699
  * func: 'race', // tells PromisE.timeout to use `PromisE.race(promises)`
@@ -612,35 +701,15 @@ declare function delayReject<T = never>(duration: number, reason?: unknown): IPr
612
701
  * timeoutMsg: 'My custom timed out message',
613
702
  * },
614
703
  * PromisE.delay(1000), // resolves after 1000ms with value 1000
615
- * PromisE.delay(2000), // resolves after 2000ms with value 2000
704
+ * () => PromisE.delay(2000), // resolves after 2000ms with value 2000
616
705
  * PromisE.delay(3000), // resolves after 3000ms with value 3000
617
706
  * ).then(console.log)
618
707
  * // Result: 1000 (Result of `Promise.race(promises)`)
619
708
  * ```
620
709
  */
621
- declare function timeout<T extends [unknown, ...unknown[]], // require at least one value
622
- Result = T['length'] extends 1 ? Awaited<T[0]> : Awaited<T[number]>[]>(timeout: number, ...values: T): IPromisE_Timeout<Result>;
623
- /**
624
- *
625
- * @param options An options object can be passed with one or more of the following properties:
626
- * @param options.func (optional) Name of the `PromisE` method to be used to combine the `values`.
627
- * Only used when more than one promise is provided.
628
- *
629
- * Accepted values:
630
- * 1. `'all'` **(default)**: for `PromisE.all`
631
- * 2. `'allSettled'`: for `PromisE.allSettled`
632
- * 3. `'any'`: for `PromisE.any`
633
- * 4. `'race'`: for `PromisE.race`
634
- *
635
- * @param options.timeout (optional) timeout duration in milliseconds. Default: `10_000` (10 seconds)
636
- * @param options.timeoutMsg (optional) custom error message to be used when promises timeout.
637
- *
638
- * @param values
639
- */
640
- declare function timeout<T extends [unknown, ...unknown[]], // require at least one value
641
- TFunc extends keyof TimeoutFunc<T>, Result = T['length'] extends 1 ? Awaited<T[0]> : Awaited<ReturnType<TimeoutFunc<T>[TFunc]>>>(options: TimeoutOptions<TFunc>, ...values: T): IPromisE_Timeout<Result>;
710
+ declare function timeout<T extends unknown[], TFunc extends keyof TimeoutFunc<T> = keyof TimeoutFunc<T>>(options: TimeoutOptions<T, TFunc>, ...values: T): IPromisE_Timeout<TimeoutResult<T, TFunc>>;
642
711
  declare namespace timeout {
643
- var defaultOptions: Required<TimeoutOptions>;
712
+ var defaults: Required<Omit<TimeoutOptions<unknown[], keyof TimeoutFunc<[]>>, "abortCtrl" | "signal">>;
644
713
  }
645
714
 
646
715
  /**
@@ -771,4 +840,4 @@ declare const retry: {
771
840
  };
772
841
  };
773
842
 
774
- export { type DeferredAsyncCallback, type DeferredAsyncDefaults, type DeferredAsyncGetPromise, type DeferredAsyncOptions, type IPromisE, type IPromisE_Delay, type IPromisE_Timeout, type OnEarlyFinalize, PromisE, PromisEBase, type PromiseParams, ResolveError, ResolveIgnored, type RetryOptions, type TimeoutFunc, type TimeoutOptions, PromisE as default, deferred, deferredCallback, delay, delayReject, retry, timeout };
843
+ export { type DeferredAsyncCallback, type DeferredAsyncDefaults, type DeferredAsyncOptions, FALLBACK_TIMEOUT, type GetPromiseFunc, type IPromisE, type IPromisE_Delay, type IPromisE_Timeout, type OnEarlyFinalize, PromisE, PromisEBase, type PromiseParams, ResolveError, ResolveIgnored, type RetryIfFunc, type RetryOptions, type TimeoutFunc, type TimeoutOptions, type TimeoutOptionsDefault, type TimeoutResult, PromisE as default, deferred, deferredCallback, delay, delayReject, retry, timeout };
package/dist/index.js CHANGED
@@ -1,33 +1,34 @@
1
1
  // src/deferred.ts
2
2
  import {
3
- deferred as deferredCore,
3
+ deferred as deferredSync,
4
4
  fallbackIfFails as fallbackIfFails2,
5
5
  isFn as isFn2,
6
6
  isPositiveNumber,
7
- objCopy,
8
- throttled as throttledCore
7
+ objCopy
9
8
  } from "@superutils/core";
10
9
 
11
10
  // src/PromisEBase.ts
12
11
  import { fallbackIfFails, isFn, isPromise } from "@superutils/core";
13
12
  var _PromisEBase = class _PromisEBase extends Promise {
14
13
  constructor(input) {
15
- if (input instanceof _PromisEBase) return input;
16
14
  let _resolve;
17
15
  let _reject;
18
16
  super((resolve, reject) => {
19
17
  _reject = (reason) => {
20
- this._state = 2;
21
18
  reject(reason);
19
+ this._state = 2;
22
20
  };
23
21
  _resolve = (value) => {
24
- this._state = 1;
25
22
  resolve(value);
23
+ this._state = 1;
26
24
  };
27
- input != null ? input : input = () => {
28
- };
29
- const promise = isPromise(input) ? input : isFn(input) ? new globalThis.Promise(input) : Promise.resolve(input);
30
- promise.then(_resolve, _reject);
25
+ if (isFn(input)) {
26
+ fallbackIfFails(input, [_resolve, _reject], _reject);
27
+ } else if (isPromise(input)) {
28
+ input.then(_resolve, _reject);
29
+ } else if (input !== void 0) {
30
+ _resolve(input);
31
+ }
31
32
  });
32
33
  this._state = 0;
33
34
  /**
@@ -66,15 +67,15 @@ var _PromisEBase = class _PromisEBase extends Promise {
66
67
  //
67
68
  /** Indicates if the promise is still pending/unfinalized */
68
69
  get pending() {
69
- return this._state === 0;
70
+ return this.state === 0;
70
71
  }
71
72
  /** Indicates if the promise has been rejected */
72
73
  get rejected() {
73
- return this._state === 2;
74
+ return this.state === 2;
74
75
  }
75
76
  /** Indicates if the promise has been resolved */
76
77
  get resolved() {
77
- return this._state === 1;
78
+ return this.state === 1;
78
79
  }
79
80
  /**
80
81
  * Get promise status code:
@@ -86,11 +87,6 @@ var _PromisEBase = class _PromisEBase extends Promise {
86
87
  get state() {
87
88
  return this._state;
88
89
  }
89
- // static withResolvers = <T = unknown>() => {
90
- // const pwr = globalThis.Promise.withResolvers<T>()
91
- // const promise = new PromisEBase<T>(pwr.promise) as IPromisE<T>
92
- // return { ...pwr, promise }
93
- // }
94
90
  };
95
91
  //
96
92
  //
@@ -107,8 +103,8 @@ _PromisEBase.any = (values) => new _PromisEBase(globalThis.Promise.any(values));
107
103
  _PromisEBase.race = (values) => new _PromisEBase(globalThis.Promise.race(values));
108
104
  /** Extends Promise.reject */
109
105
  _PromisEBase.reject = (reason) => {
110
- const { promise, reject } = _PromisEBase.withResolvers();
111
- queueMicrotask(() => reject(reason));
106
+ const promise = new _PromisEBase();
107
+ queueMicrotask(() => promise.reject(reason));
112
108
  return promise;
113
109
  };
114
110
  /** Sugar for `new PromisE(Promise.resolve(...))` */
@@ -186,12 +182,14 @@ function deferred(options = {}) {
186
182
  let { onError, onIgnore, onResult } = options;
187
183
  const {
188
184
  delayMs = 0,
185
+ ignoreStale,
189
186
  resolveError,
190
187
  resolveIgnored,
191
188
  thisArg,
192
189
  throttle
193
190
  } = options;
194
- let prevQItem = null;
191
+ let lastInSeries = null;
192
+ let lastExecuted;
195
193
  const queue = /* @__PURE__ */ new Map();
196
194
  const isSequential = !isPositiveNumber(delayMs);
197
195
  if (thisArg !== void 0) {
@@ -199,17 +197,21 @@ function deferred(options = {}) {
199
197
  onIgnore = onIgnore == null ? void 0 : onIgnore.bind(thisArg);
200
198
  onResult = onResult == null ? void 0 : onResult.bind(thisArg);
201
199
  }
202
- const handleIgnore = (items, prevQItem2) => {
200
+ const handleIgnore = (items) => {
203
201
  for (const [iId, iItem] of items) {
204
202
  queue.delete(iId);
205
- if (iItem === void 0 || iItem.started) continue;
206
- onIgnore && fallbackIfFails2(onIgnore, [iItem.getPromise], 0);
203
+ const isStale = ignoreStale && iItem.sequence < lastExecuted.sequence;
204
+ if (iItem.resolved || iItem.started && !isStale) continue;
205
+ if (iItem.started) {
206
+ iItem.getPromise = (() => iItem.result);
207
+ }
208
+ fallbackIfFails2(onIgnore, [iItem.getPromise], 0);
207
209
  switch (resolveIgnored) {
208
210
  case "WITH_UNDEFINED" /* WITH_UNDEFINED */:
209
211
  iItem.resolve(void 0);
210
212
  break;
211
213
  case "WITH_LAST" /* WITH_LAST */:
212
- prevQItem2 == null ? void 0 : prevQItem2.then(iItem.resolve, iItem.reject);
214
+ lastExecuted == null ? void 0 : lastExecuted.then(iItem.resolve, iItem.reject);
213
215
  break;
214
216
  case "NEVER" /* NEVER */:
215
217
  break;
@@ -218,33 +220,32 @@ function deferred(options = {}) {
218
220
  if (!queue.size) sequence = 0;
219
221
  };
220
222
  const handleRemaining = (currentId) => {
221
- const _prevQItem = prevQItem;
222
- prevQItem = null;
223
+ lastInSeries = null;
223
224
  if (isSequential) {
224
225
  queue.delete(currentId);
225
226
  const [nextId, nextItem] = [...queue.entries()][0] || [];
226
227
  return nextId && nextItem && handleItem(nextId, nextItem);
227
228
  }
228
229
  let items = [...queue.entries()];
229
- if (throttle && options.trailing) {
230
+ if (throttle === true && options.trailing) {
230
231
  const currentIndex = items.findIndex(([id]) => id === currentId);
231
232
  items = items.slice(0, currentIndex);
232
233
  } else if (!throttle) {
233
234
  items = items.slice(0, -1);
234
235
  }
235
- handleIgnore(items, _prevQItem);
236
+ handleIgnore(items);
237
+ queue.delete(currentId);
236
238
  };
237
- let prevSeq = 0;
238
239
  const executeItem = async (id, qItem) => {
239
240
  var _a;
240
- qItem.started = true;
241
- const _prevQItem = prevQItem;
242
- prevQItem = qItem;
243
- prevSeq = (_a = prevQItem == null ? void 0 : prevQItem.sequence) != null ? _a : 0;
244
241
  try {
245
- const result = await PromisEBase_default.try(qItem.getPromise);
246
- const ignore = !isSequential && options.ignoreStale && prevSeq > qItem.sequence;
247
- if (ignore) return handleIgnore([[id, qItem]], _prevQItem);
242
+ qItem.started = true;
243
+ lastExecuted = qItem;
244
+ lastInSeries = qItem;
245
+ (_a = qItem.result) != null ? _a : qItem.result = PromisEBase_default.try(qItem.getPromise);
246
+ const result = await qItem.result;
247
+ const isStale = !!ignoreStale && qItem.sequence < lastExecuted.sequence;
248
+ if (isStale) return handleIgnore([[id, qItem]]);
248
249
  qItem.resolve(result);
249
250
  onResult && fallbackIfFails2(onResult, [result], void 0);
250
251
  } catch (err) {
@@ -265,22 +266,21 @@ function deferred(options = {}) {
265
266
  }
266
267
  handleRemaining(id);
267
268
  };
268
- const handleItem = isSequential ? executeItem : (throttle ? throttledCore : deferredCore)(
269
+ const handleItem = isSequential ? executeItem : deferredSync(
269
270
  executeItem,
270
271
  delayMs,
271
272
  options
272
273
  );
273
- const deferredFunc = (promise) => {
274
+ return (promise) => {
274
275
  const id = /* @__PURE__ */ Symbol("deferred-queue-item-id");
275
276
  const qItem = new PromisEBase_default();
276
277
  qItem.getPromise = isFn2(promise) ? promise : () => promise;
277
278
  qItem.started = false;
278
279
  qItem.sequence = ++sequence;
279
280
  queue.set(id, qItem);
280
- if (!prevQItem || !isSequential) handleItem(id, qItem);
281
+ if (!lastInSeries || !isSequential) handleItem(id, qItem);
281
282
  return qItem;
282
283
  };
283
- return deferredFunc;
284
284
  }
285
285
  deferred.defaults = {
286
286
  /**
@@ -307,23 +307,28 @@ var deferredCallback_default = deferredCallback;
307
307
 
308
308
  // src/delay.ts
309
309
  import { fallbackIfFails as fallbackIfFails3, isFn as isFn3 } from "@superutils/core";
310
- function delay(duration = delay.defaults.duration, result = duration, asRejected = false) {
310
+ function delay(duration = delay.defaults.duration, result, asRejected = false) {
311
311
  const promise = new PromisEBase_default();
312
312
  const finalize = (result2) => {
313
- var _a;
314
- if (isFn3(result2))
315
- result2 = (_a = fallbackIfFails3(result2, [], void 0)) != null ? _a : duration;
316
- if (!asRejected) return promise.resolve(result2);
317
- promise.reject(
318
- duration !== result2 && result2 !== void 0 ? result2 : new Error(
319
- `${delay.defaults.delayTimeoutMsg} ${duration}ms`
320
- )
313
+ const _result = fallbackIfFails3(
314
+ async () => {
315
+ const _result2 = await (isFn3(result2) ? result2() : result2);
316
+ return !asRejected ? _result2 != null ? _result2 : duration : _result2 != null ? _result2 : new Error(
317
+ `${delay.defaults.delayTimeoutMsg} ${duration}ms`
318
+ );
319
+ },
320
+ [],
321
+ // when result is a function and it fails/rejects,
322
+ // promise will reject even if `asRejected = false`
323
+ (err) => Promise.reject(err)
321
324
  );
325
+ !asRejected ? promise.resolve(_result) : _result.then(promise.reject, promise.reject);
322
326
  };
323
327
  promise.timeoutId = setTimeout(() => finalize(result), duration);
324
328
  promise.pause = () => clearTimeout(promise.timeoutId);
325
329
  promise.catch(() => {
326
330
  }).finally(() => promise.pause());
331
+ promise.onEarlyFinalize.push(() => promise.pause());
327
332
  return promise;
328
333
  }
329
334
  delay.defaults = {
@@ -348,6 +353,7 @@ import {
348
353
  objCopy as objCopy2
349
354
  } from "@superutils/core";
350
355
  var retry = async (func, options) => {
356
+ var _a, _b;
351
357
  options = objCopy2(retry.defaults, options != null ? options : {}, [], (key, value) => {
352
358
  switch (key) {
353
359
  // case 'retryDelayJitter':
@@ -377,18 +383,21 @@ var retry = async (func, options) => {
377
383
  if (retryBackOff === "exponential" && retryCount > 1) _retryDelay *= 2;
378
384
  if (retryDelayJitter)
379
385
  _retryDelay += Math.floor(Math.random() * retryDelayJitterMax);
380
- retryCount > 0 && await delay_default(_retryDelay);
386
+ if (retryCount > 0) await delay_default(_retryDelay);
381
387
  try {
382
388
  error = void 0;
383
389
  result = await func();
384
390
  } catch (err) {
385
391
  error = err;
386
392
  }
387
- shouldRetry = maxRetries > 0 && retryCount < maxRetries && (!!error || !!await fallbackIfFails4(
388
- options.retryIf,
389
- [result, retryCount],
390
- false
391
- ));
393
+ if (maxRetries === 0 || retryCount >= maxRetries) break;
394
+ shouldRetry = !!((_b = await fallbackIfFails4(
395
+ (_a = options.retryIf) != null ? _a : error,
396
+ // if `retryIf` not provided, retry on error
397
+ [result, retryCount, error],
398
+ error
399
+ // if `retryIf` throws error, default to retry on error
400
+ )) != null ? _b : error);
392
401
  } while (shouldRetry);
393
402
  if (error !== void 0) return Promise.reject(error);
394
403
  return result;
@@ -403,44 +412,116 @@ retry.defaults = {
403
412
  var retry_default = retry;
404
413
 
405
414
  // src/timeout.ts
406
- import { isFn as isFn4, isObj, isPositiveNumber as isPositiveNumber2 } from "@superutils/core";
407
- function timeout(timeout2, ...values) {
408
- var _a, _b;
409
- let funcName = "all";
410
- let timeoutMsg = "";
411
- if (isObj(timeout2)) {
412
- funcName = timeout2.func;
413
- timeoutMsg = (_a = timeout2.timeoutMsg) != null ? _a : "";
414
- timeout2 = (_b = timeout2.timeout) != null ? _b : 1e4;
415
- }
416
- timeout2 = isPositiveNumber2(timeout2) && timeout2 || 1e4;
417
- const func = isFn4(PromisEBase_default[funcName]) ? PromisEBase_default[funcName] : PromisEBase_default.all;
418
- const dataPromise = values.length <= 1 ? new PromisEBase_default(values == null ? void 0 : values[0]) : func(values);
419
- const timeoutPromise = delayReject_default(
420
- timeout2,
421
- new Error(timeoutMsg || `Timed out after ${timeout2}ms`)
415
+ import {
416
+ arrUnique,
417
+ fallbackIfFails as fallbackIfFails5,
418
+ isFn as isFn4,
419
+ isObj,
420
+ isPositiveNumber as isPositiveNumber2,
421
+ noop,
422
+ objCopy as objCopy3
423
+ } from "@superutils/core";
424
+ var FALLBACK_TIMEOUT = 1e4;
425
+ function timeout(timeoutOrOptions, ...values) {
426
+ const options = objCopy3(
427
+ timeout.defaults,
428
+ isObj(timeoutOrOptions) ? timeoutOrOptions : { timeout: timeoutOrOptions },
429
+ [],
430
+ "empty"
431
+ );
432
+ const { func, onTimeout } = options;
433
+ const duration = isPositiveNumber2(options.timeout) ? options.timeout : FALLBACK_TIMEOUT;
434
+ const arrPromises = values.map((v) => isFn4(v) ? PromisEBase_default.try(v) : v);
435
+ const dataPromise = arrPromises.length <= 1 ? (
436
+ // single promise resolves to a single result
437
+ arrPromises[0] instanceof PromisEBase_default ? arrPromises[0] : new PromisEBase_default(arrPromises[0])
438
+ ) : (
439
+ // multiple promises resolve to an array of results
440
+ (isFn4(PromisEBase_default[func]) ? PromisEBase_default[func] : PromisEBase_default.all)(
441
+ arrPromises
442
+ )
422
443
  );
444
+ const timeoutPromise = delayReject_default(duration, onTimeout);
423
445
  const promise = PromisEBase_default.race([
424
446
  dataPromise,
425
447
  timeoutPromise
426
448
  ]);
427
- promise.clearTimeout = () => clearTimeout(timeoutPromise.timeoutId);
428
- promise.data = dataPromise;
429
- promise.timeout = timeoutPromise;
430
- Object.defineProperty(promise, "timedout", {
431
- get: () => promise.timeout.rejected
432
- });
433
- dataPromise.catch(() => {
434
- }).finally(() => {
435
- promise.clearTimeout();
436
- });
449
+ addPropsNListeners(promise, dataPromise, timeoutPromise, options);
437
450
  return promise;
438
451
  }
439
- timeout.defaultOptions = {
452
+ timeout.defaults = {
440
453
  func: "all",
441
- timeout: 1e4
454
+ timeout: FALLBACK_TIMEOUT
442
455
  };
443
456
  var timeout_default = timeout;
457
+ var addPropsNListeners = (promise, dataPromise, timeoutPromise, options) => {
458
+ const { abortCtrl, onAbort, signal } = options;
459
+ const signals = arrUnique([abortCtrl == null ? void 0 : abortCtrl.signal, signal].filter(Boolean));
460
+ Object.defineProperties(promise, {
461
+ aborted: {
462
+ get() {
463
+ return promise.rejected && !!signals.find((s) => s.aborted);
464
+ }
465
+ },
466
+ cancelAbort: {
467
+ get() {
468
+ return () => {
469
+ signals == null ? void 0 : signals.forEach(
470
+ (signal2) => signal2.removeEventListener("abort", handleAbort)
471
+ );
472
+ };
473
+ }
474
+ },
475
+ clearTimeout: {
476
+ get() {
477
+ return () => clearTimeout(timeoutPromise.timeoutId);
478
+ }
479
+ },
480
+ data: {
481
+ get() {
482
+ return dataPromise;
483
+ }
484
+ },
485
+ timeout: {
486
+ get() {
487
+ return timeoutPromise;
488
+ }
489
+ },
490
+ timedout: {
491
+ get() {
492
+ return promise.rejected && timeoutPromise.rejected;
493
+ }
494
+ }
495
+ });
496
+ const cleanup = () => {
497
+ promise.cancelAbort();
498
+ promise.clearTimeout();
499
+ };
500
+ promise.onEarlyFinalize.push(cleanup);
501
+ promise.catch(() => {
502
+ var _a;
503
+ if (!timeoutPromise.rejected && !signals.find((x) => x.aborted))
504
+ return;
505
+ ((_a = abortCtrl == null ? void 0 : abortCtrl.signal) == null ? void 0 : _a.aborted) === false && abortCtrl.abort();
506
+ }).finally(cleanup);
507
+ if (!signals.length) return;
508
+ const started = /* @__PURE__ */ new Date();
509
+ function handleAbort() {
510
+ if (!promise.pending) return;
511
+ fallbackIfFails5(async () => await (onAbort == null ? void 0 : onAbort()), [], void 0).then(
512
+ (err) => {
513
+ var _a;
514
+ err != null ? err : err = new Error(
515
+ `Aborted after ${(/* @__PURE__ */ new Date()).getTime() - started.getTime()}ms`
516
+ );
517
+ (_a = err.name) != null ? _a : err.name = "AbortError";
518
+ promise.reject(err);
519
+ },
520
+ noop
521
+ );
522
+ }
523
+ signals.forEach((signal2) => signal2.addEventListener("abort", handleAbort));
524
+ };
444
525
 
445
526
  // src/PromisE.ts
446
527
  var PromisE = class extends PromisEBase_default {
@@ -456,6 +537,7 @@ var PromisE_default = PromisE;
456
537
  // src/index.ts
457
538
  var index_default = PromisE_default;
458
539
  export {
540
+ FALLBACK_TIMEOUT,
459
541
  PromisE,
460
542
  PromisEBase,
461
543
  ResolveError,
package/package.json CHANGED
@@ -4,9 +4,9 @@
4
4
  "url": "https://github.com/alien45/superutils/issues"
5
5
  },
6
6
  "dependencies": {
7
- "@superutils/core": "^1.1.5"
7
+ "@superutils/core": "^1.1.8"
8
8
  },
9
- "description": "An extended Promise with extra features such as status tracking, deferred/throttled execution, timeout and retry mechanism.",
9
+ "description": "An extended Promise with additional features such as status tracking, deferred/throttled execution, timeout and retry mechanism.",
10
10
  "files": [
11
11
  "dist",
12
12
  "README.md",
@@ -23,7 +23,7 @@
23
23
  "main": "dist/index.js",
24
24
  "name": "@superutils/promise",
25
25
  "peerDpendencies": {
26
- "@superutils/core": "^1.1.5"
26
+ "@superutils/core": "^1.2.0"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public"
@@ -43,5 +43,5 @@
43
43
  "sideEffects": false,
44
44
  "type": "module",
45
45
  "types": "dist/index.d.ts",
46
- "version": "1.1.5"
46
+ "version": "1.2.0"
47
47
  }