@superutils/promise 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Toufiqur Rahaman Chowdhury
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # @superutils/promise
2
+
3
+ An extended `Promise` implementation, named `PromisE`, that provides additional features and utilities for easier asynchronous flow control in JavaScript and TypeScript applications.
4
+
5
+ This package offers a drop-in replacement for the native `Promise` that includes status tracking (`.pending`, `.resolved`, `.rejected`) and a suite of powerful static methods for common asynchronous patterns like deferred execution, throttling, and cancellable fetches.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Features](#features)
10
+ - [Installation](#installation)
11
+ - [Usage](#usage)
12
+ - [`new PromisE(executor)`](#promise-executor): Drop-in replacement for `Promise`
13
+ - [`new PromisE(promise)`](#promise-status): Check promise status
14
+ - [`PromisE.try()`](#static-methods): Static methods
15
+ - [`PromisE.delay()`](#delay): Async delay
16
+ - [`PromisE.deferred()`](#deferred): Async debounced/throttled callback
17
+ - [`PromisE.timeout()`](#timeout): Reject after timeout
18
+
19
+ ## Features
20
+
21
+ - **Promise Status**: Easily check if a promise is `pending`, `resolved`, or `rejected`.
22
+ - **Deferred Execution**: Defer or throttle promise-based function calls with `PromisE.deferred()`.
23
+ - **Auto-cancellable Fetch**: Automatically abort pending requests when subsequent requests are made using `PromisE.deferredFetch()` and `PromisE.deferredPost()`.
24
+ - **Auto-cancellable Fetch**: The `PromisE.deferredFetch` and `PromisE.deferredPost` utilities automatically abort pending requests when a new deferred/throttled call is made.
25
+ - **Timeouts**: Wrap any promise with a timeout using `PromisE.timeout()`.
26
+ - **Rich Utilities**: A collection of static methods like `.all()`, `.race()`, `.delay()`, and more, all returning `PromisE` instances.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @superutils/core @superutils/promise
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ <div id="promise-executor"></div>
37
+
38
+ ### `new PromisE(executor)`: Drop-in replacement for `Promise`
39
+
40
+ The `PromisE` class can be used just like the native `Promise`. The key difference is the addition of status properties:
41
+
42
+ ```typescript
43
+ import { PromisE } from '@superutils/promise'
44
+
45
+ const p = new PromisE(resolve => setTimeout(() => resolve('done'), 1000))
46
+
47
+ console.log(p.pending) // true
48
+
49
+ p.then(result => {
50
+ console.log(result) // 'done'
51
+ console.log(p.resolved) // true
52
+ console.log(p.pending) // false
53
+ })
54
+ ```
55
+
56
+ and the ability to early finalize a promise:
57
+
58
+ ```typescript
59
+ import { PromisE } from '@superutils/promise'
60
+ const p = new PromisE(resolve => setTimeout(() => resolve('done'), 10000))
61
+ p.then(result => console.log(result))
62
+ // resolve the promise early
63
+ setTimeout(() => p.resolve('finished early'), 500)
64
+ ```
65
+
66
+ <div id="static-methods"></div>
67
+
68
+ ### `PromisE.try(fn)`: Static methods
69
+
70
+ Drop-in replacement for all `Promise` static methods such as `.all()`, `.race()`, `.reject`, `.resolve`, `.try()`, `.withResolvers()`....
71
+
72
+ ```typescript
73
+ import { PromisE } from '@superutils/promise'
74
+
75
+ const p = PromisE.try(() => {
76
+ throw new Error('Something went wrong')
77
+ })
78
+
79
+ p.catch(error => {
80
+ console.error(error.message) // 'Something went wrong'
81
+ console.log(p.rejected) // true
82
+ })
83
+ ```
84
+
85
+ <div id="promise-status"></div>
86
+
87
+ ### `new PromisE(promise)`
88
+
89
+ Check status of an existing promise.
90
+
91
+ ```typescript
92
+ import { PromisE } from '@superutils/promise'
93
+ const x = Promise.resolve(1)
94
+ const p = new PromisE(x)
95
+ console.log(p.pending) // false
96
+ console.log(p.resolved) // true
97
+ console.log(p.rejected) // false
98
+ ```
99
+
100
+ <div id="delay"></div>
101
+
102
+ ### `PromisE.delay(duration)`: Async delay
103
+
104
+ Creates a promise that resolves after a specified duration, essentially a promise-based `setTimeout`.
105
+
106
+ ```typescript
107
+ import PromisE from '@superutils/promise'
108
+ // Wait until `appReady` becomes truthy but
109
+ while (!appReady) {
110
+ await PromisE.delay(100)
111
+ }
112
+ ```
113
+
114
+ #### `PromisE.delay(duration, callback)`: execute after delay
115
+
116
+ Creates a promise that executes a function after a specified duration and returns the value the function returns.
117
+
118
+ If callback returns undefined, default value will be the duration.
119
+
120
+ ```typescript
121
+ import PromisE from '@superutils/promise'
122
+
123
+ const callback = () => {
124
+ /* do stuff here */
125
+ }
126
+ await PromisE.delay(100, callback)
127
+ ```
128
+
129
+ <div id="deferred"></div>
130
+
131
+ ### `PromisE.deferred(options)`: async debounced/throttled execution
132
+
133
+ Create a function that debounces or throttles promise-returning function calls. This is useful for scenarios like auto-saving user input or preventing multiple rapid API calls.
134
+
135
+ ```typescript
136
+ import PromisE, { ResolveIgnored } from '@superutils/promise'
137
+
138
+ // Create a deferred function that waits 300ms after the last call
139
+ const deferredSave = PromisE.deferred({
140
+ defer: 300,
141
+ /** ignored promises will resolve with `undefined` */
142
+ resolveIgnored: ResolveIgnored.WITH_UNDEFINED,
143
+
144
+ /** ignored promises will NEVER be resolved/rejected
145
+ * USE WITH CAUTION!
146
+ */
147
+ resolveIgnored: ResolveIgnored.NEVER,
148
+
149
+ // ignored promises will resolve with the result of the last call
150
+ resolveIgnored: ResolveIgnored.WITH_LAST, // (default)
151
+ })
152
+
153
+ // Simulate rapid calls
154
+ deferredSave(() => api.save({ text: 'first' }))
155
+ deferredSave(() => api.save({ text: 'second' }))
156
+ // Only the 3rd call is executed.
157
+ // But all of them are resolved with the result of the 3rd call when `resolveIgnored` is `ResolveIgnored.WITH_LAST`
158
+ deferredSave(() => api.save({ text: 'third' })).then(response =>
159
+ console.log('Saved!', response),
160
+ )
161
+ ```
162
+
163
+ <div id="deferredCallback"></div>
164
+
165
+ ### `PromisE.deferredCallback(callback, options)`: async debounced/throttled callbacks
166
+
167
+ Same as `PromisE.deferred` but for event handlers etc.
168
+
169
+ ```typescript
170
+ import PromisE from '@superutils/promise'
171
+
172
+ // Input change handler
173
+ const handleChange = (e: { target: { value: number } }) =>
174
+ console.log(e.target.value)
175
+ // Change handler with `PromisE.deferred()`
176
+ const handleChangeDeferred = PromisE.deferredCallback(handleChange, {
177
+ delayMs: 300,
178
+ throttle: false,
179
+ })
180
+ // Simulate input change events after prespecified delays
181
+ const delays = [100, 150, 200, 550, 580, 600, 1000, 1100]
182
+ delays.forEach(timeout =>
183
+ setTimeout(
184
+ () => handleChangeDeferred({ target: { value: timeout } }),
185
+ timeout,
186
+ ),
187
+ )
188
+ // Prints:
189
+ // 200, 600, 1100
190
+ ```
191
+
192
+ <div id="timeout"></div>
193
+
194
+ ### `PromisE.timeout(duration, ...promises)`: Reject after timeout
195
+
196
+ #### Reject stuck or unexpectedly lenghthy promise(s) after a specified timeout:
197
+
198
+ ```typescript
199
+ import { PromisE } from '@superutils/promise'
200
+
201
+ PromisE.timeout(
202
+ 5000, // timeout after 5000ms
203
+ api.save({ text: 'takes longer than 5s to finish' }),
204
+ ).catch(console.log)
205
+ // Error: Error('Timed out after 5000ms')
206
+ ```
207
+
208
+ #### Show a message when loading is too long:
209
+
210
+ ```typescript
211
+ import { PromisE } from '@superutils/promise'
212
+
213
+ const loadUserNProducts = () => {
214
+ const promise = PromisE.timeout(
215
+ 5000, // timeout after 5000ms
216
+ api.getUser(),
217
+ api.getProducts(),
218
+ )
219
+ const [user, products] = await promise.catch(err => {
220
+ // promise did not time out, but was rejected
221
+ // because one of the data promises rejected
222
+ if (!promise.timedout) return Promise.reject(err)
223
+
224
+ // promise timed out >> print/update UI
225
+ console.log('Request is taking longer than expected......')
226
+ // now return the "data promise", the promise(s) provided in the PromisE.timeout()
227
+ // If more than one promises provided, then `promise.data` will be the combination of them all: `PromisE.all(...promises)`
228
+ return promise.data
229
+ })
230
+ return [user, products]
231
+ }
232
+ loadUserNProducts()
233
+ ```
@@ -0,0 +1,617 @@
1
+ import * as _superutils_core from '@superutils/core';
2
+ import { ValueOrPromise, TimeoutId, ThrottleConfig, DeferredConfig } from '@superutils/core';
3
+
4
+ interface IPromisE<T = unknown> extends Promise<T> {
5
+ /** 0: pending, 1: resolved, 2: rejected */
6
+ readonly state: 0 | 1 | 2;
7
+ /** callbacks to be invoked whenever PromisE instance is finalized early using non-static resolve/reject methods */
8
+ onEarlyFinalize: OnEarlyFinalize<T>[];
9
+ /** Indicates if the promise is still pending/unfinalized */
10
+ readonly pending: boolean;
11
+ /** Reject pending promise early. */
12
+ reject: (reason: unknown) => void;
13
+ /** Indicates if the promise has been rejected */
14
+ readonly rejected: boolean;
15
+ /** Resovle pending promise early. */
16
+ resolve: (value: T | PromiseLike<T>) => void;
17
+ /** Indicates if the promise has been resolved */
18
+ readonly resolved: boolean;
19
+ }
20
+ interface IPromisE_Delay<T = unknown> extends 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
+ timeoutId: TimeoutId;
62
+ }
63
+ /**
64
+ * Descibes a timeout PromisE and it's additional properties.
65
+ */
66
+ type IPromisE_Timeout<T = unknown> = IPromisE<T> & {
67
+ /** Clearing the timeout will prevent it from timing out */
68
+ clearTimeout: () => void;
69
+ /** The result/data promise. If more than one supplied in `args` result promise will be a combined `PromisE.all` */
70
+ data: IPromisE<T>;
71
+ /** A shorthand getter to check if the promise has timed out. Same as `promise.timeout.rejected`. */
72
+ readonly timedout: boolean;
73
+ /** The timeout promise */
74
+ timeout: IPromisE_Delay<T>;
75
+ };
76
+ type OnEarlyFinalize<T> = <TResolved extends boolean, TValue = TResolved extends true ? T : unknown>(resolved: TResolved, resultOrReason: TValue) => ValueOrPromise<unknown>;
77
+ type PromiseParams<T = unknown> = ConstructorParameters<typeof Promise<T>>;
78
+
79
+ /** Return type of `PromisE.deferred()` */
80
+ type DeferredReturn<TArgs extends unknown[] | [] = []> = <TResult = unknown>(promise: Promise<TResult> | ((...args: TArgs) => Promise<TResult>)) => IPromisE<TResult>;
81
+ type DeferredOptions<ThisArg = unknown> = {
82
+ /** Delay in milliseconds, used for `debounce` and `throttle` modes. */
83
+ delayMs?: number;
84
+ /** Callback invoked whenever promise/function throws error */
85
+ onError?: (err: unknown) => ValueOrPromise<unknown>;
86
+ /**
87
+ * Whenever a promise/function is ignored when in debource/throttle mode, `onIgnored` wil be invoked.
88
+ * The promise/function will not be invoked, unless it's manually invoked using the `ignored` function.
89
+ * Use for debugging or logging purposes.
90
+ */
91
+ onIgnore?: (ignored: () => Promise<unknown>) => ValueOrPromise<unknown>;
92
+ /**
93
+ * Whenever a promise/function is executed successfully `onResult` will be called.
94
+ * Those that are ignored but resolve with last will not cause `onResult` to be invoked.
95
+ *
96
+ * Result can be `undefined` if `ResolveIgnored.WITH_UNDEFINED` is used.
97
+ */
98
+ onResult?: (result?: unknown) => ValueOrPromise<unknown>;
99
+ /**
100
+ * Indicates what to do when a promise in the queue is ignored.
101
+ * See {@link ResolveIgnored} for available options.
102
+ */
103
+ resolveIgnored?: ResolveIgnored;
104
+ resolveError?: ResolveError;
105
+ /** Enable throttle mode. Requires {@link DeferredOptions.delayMs}*/
106
+ throttle?: boolean;
107
+ } & (({
108
+ delayMs: number;
109
+ throttle: true;
110
+ } & ThrottleConfig<ThisArg>) | ({
111
+ delayMs?: number;
112
+ throttle?: false;
113
+ } & DeferredConfig<ThisArg>));
114
+ /** Options for what to do when deferred promise/function fails */
115
+ declare enum ResolveError {
116
+ /** Neither resolve nor reject the failed */
117
+ NEVER = "NEVER",
118
+ /** (default) Reject the failed as usual */
119
+ REJECT = "REJECT",
120
+ /** Resolve (not reject) with the error/reason */
121
+ WITH_ERROR = "RESOLVE_ERROR",
122
+ /** Resolve with undefined */
123
+ WITH_UNDEFINED = "RESOLVE_UNDEFINED"
124
+ }
125
+ /** Options for what to do when a promise/callback is ignored, either because of being deferred, throttled or another been prioritized. */
126
+ declare enum ResolveIgnored {
127
+ /** Never resolve ignored promises. Caution: make sure this doesn't cause any memory leaks. */
128
+ NEVER = "NEVER",
129
+ /** (default) resolve with active promise result, the one that caused the current promise/callback to be ignored). */
130
+ WITH_LAST = "WITH_LAST",
131
+ /** resolve with `undefined` value */
132
+ WITH_UNDEFINED = "WITH_UNDEFINED"
133
+ }
134
+
135
+ /** Global configuration */
136
+ declare const config: {
137
+ /** Default value for `options` used by `PromisE.*deferred*` functions */
138
+ deferOptions: DeferredOptions;
139
+ delayTimeoutMsg: string;
140
+ retryOptions: {
141
+ retry: number;
142
+ retryBackOff: "exponential";
143
+ retryDelay: number;
144
+ retryDelayJitter: true;
145
+ retryDelayJitterMax: number;
146
+ retryIf: null;
147
+ };
148
+ };
149
+ type Config = typeof config;
150
+
151
+ /** Options for automatic retry mechanism */
152
+ type RetryOptions<T = unknown> = {
153
+ /**
154
+ * Maximum number of retries.
155
+ *
156
+ * The total number of attempts will be `retry + 1`.
157
+ *
158
+ * Default: `1`
159
+ */
160
+ retry?: number;
161
+ /**
162
+ * Accepted values:
163
+ * - exponential: each subsequent retry delay will be doubled from the last
164
+ * - linear: fixed delay between retries
165
+ * Default: 'exponential'
166
+ */
167
+ retryBackOff?: 'exponential' | 'linear';
168
+ /**
169
+ * Delay in milliseconds between retries.
170
+ * Default: `300`
171
+ */
172
+ retryDelay?: number;
173
+ /**
174
+ * Add a random delay between 0ms and `retryDelayJitterMax` to the `retryDelayMs`.
175
+ * Default: `true`
176
+ */
177
+ retryDelayJitter?: boolean;
178
+ /**
179
+ * Maximum delay (in milliseconds) to be used when randomly generating jitter delay duration.
180
+ * Default: `100`
181
+ */
182
+ retryDelayJitterMax?: number;
183
+ /**
184
+ * Additional condition/function to be used to determine whether function should be retried.
185
+ * `retryIf` will only be executed when function execution is successful.
186
+ */
187
+ retryIf?: null | ((prevResult: T | undefined, retryCount: number, error?: unknown) => boolean);
188
+ };
189
+
190
+ /**
191
+ * @function PromisE.deferred
192
+ * The adaptation of the `deferred()` function tailored for Promises.
193
+ *
194
+ * @param options (optional) options
195
+ * @property options.delayMs (optional) delay in milliseconds to be used with debounce & throttle modes. When `undefined` or `>= 0`, execution will be sequential.
196
+ * @property options.onError (optional)
197
+ * @property options.onIgnore (optional) invoked whenever callback invocation is ignored by a newer invocation
198
+ * @property options.onResult (optional)
199
+ * @property options.resolveIgnored (optional) see {@link ResolveIgnored}.
200
+ * Default: `PromisE.defaultResolveIgnord` (changeable)
201
+ *
202
+ * @property options.throttle (optional) toggle to switch between debounce/deferred and throttle mode.
203
+ * Requires `defer`.
204
+ * Default: `false`
205
+ *
206
+ * @returns {Function} a callback that is invoked in one of the followin 3 methods:
207
+ * - sequential: when `delayMs <= 0` or `delayMs = undefined`
208
+ * - debounced: when `delayMs > 0` and `throttle = false`
209
+ * - throttled: when `delayMs > 0` and `throttle = true`
210
+ *
211
+ * The main difference is that:
212
+ * - Notes:
213
+ * 1. A "request" simply means invokation of the returned callback function
214
+ * 2. By "handled" it means a "request" will be resolved or rejected.
215
+ * - `PromisE.deferred` is to be used with promises/functions
216
+ * - There is no specific time delay.
217
+ * - The time when a request is completed is irrelevant.
218
+ * - If not throttled:
219
+ * 1. Once a request is handled, all previous requests will be ignored and pool starts anew.
220
+ * 2. If a function is provided in the returned callback, ALL of them will be invoked, regardless of pool size.
221
+ * 3. The last/only request in an on-going requests' pool will handled (resolve/reject).
222
+ * - If throttled:
223
+ * 1. Once a requst starts executing, subsequent requests will be added to a queue.
224
+ * 2. The last/only item in the queue will be handled. Rest will be ignored.
225
+ * 3. If a function is provided in the returned callback, it will be invoked only if the request is handled.
226
+ * Thus, improving performance by avoiding unnecessary invokations.
227
+ * 4. If every single request/function needs to be invoked, avoid using throttle.
228
+ *
229
+ * - If throttled and `strict` is truthy, all subsequent request while a request is being handled will be ignored.
230
+ *
231
+ * @example Explanation & example usage:
232
+ * ```typescript
233
+ * const example = throttle => {
234
+ * const df = PromisE.deferred(throttle)
235
+ * df(() => PromisE.delay(5000)).then(console.log)
236
+ * df(() => PromisE.delay(500)).then(console.log)
237
+ * df(() => PromisE.delay(1000)).then(console.log)
238
+ * // delay 2 seconds and invoke df() again
239
+ * setTimeout(() => {
240
+ * df(() => PromisE.delay(200)).then(console.log)
241
+ * }, 2000)
242
+ * }
243
+ *
244
+ * // Without throttle
245
+ * example(false)
246
+ * // `1000` and `200` will be printed in the console
247
+ *
248
+ * // with throttle
249
+ * example(true)
250
+ * // `5000` and `200` will be printed in the console
251
+ *
252
+ * // with throttle with strict mode
253
+ * example(true)
254
+ * // `5000` will be printed in the console
255
+ * ```
256
+ */
257
+ declare function deferred<T>(options?: DeferredOptions): DeferredReturn;
258
+
259
+ /**
260
+ * @function PromisE.deferredCallback
261
+ *
262
+ * @summary a `PromisE.deferred()` wrapper for callbacks and event handlers.
263
+ *
264
+ * @returns deferred/throttled function
265
+ *
266
+ *
267
+ * @example Debounce/deferred event handler
268
+ * ```typescript
269
+ * const handleChange = (e: { target: { value: number }}) => console.log(e.target.value)
270
+ * const handleChangeDeferred = PromisE.deferredCallback(handleChange, {
271
+ * delayMs: 300,
272
+ * throttle: false,
273
+ * })
274
+ * // simulate click events call after prespecified delay
275
+ * const delays = [
276
+ * 100,
277
+ * 150,
278
+ * 200,
279
+ * 550,
280
+ * 580,
281
+ * 600,
282
+ * 1000,
283
+ * 1100,
284
+ * ]
285
+ * delays.forEach(timeout =>
286
+ * setTimeout(() => handleChangeDeferred({
287
+ * target: { value: timeout }
288
+ * }), timeout)
289
+ * )
290
+ *
291
+ *
292
+ * // Prints:
293
+ * // 200, 600, 1100
294
+ * ```
295
+ *
296
+ * @example Throttled event handler
297
+ * ```typescript
298
+ * const handleChange = (e: { target: { value: number }}) => console.log(e.target.value)
299
+ * const handleChangeDeferred = PromisE.deferredCallback(handleChange, {
300
+ * delayMs: 300,
301
+ * throttle: true,
302
+ * })
303
+ * // simulate click events call after prespecified delay
304
+ * const delays = [
305
+ * 100,
306
+ * 150,
307
+ * 200,
308
+ * 550,
309
+ * 580,
310
+ * 600,
311
+ * 1000,
312
+ * 1100,
313
+ * ]
314
+ * delays.forEach(timeout =>
315
+ * setTimeout(() => handleChangeDeferred({
316
+ * target: { value: timeout }
317
+ * }), timeout)
318
+ * )
319
+ * // Prints: 100, 550, 1100
320
+ * ```
321
+ */
322
+ declare function deferredCallback<TDefault, CbArgs extends unknown[] = unknown[]>(callback: (...args: CbArgs) => TDefault | Promise<TDefault>, options?: DeferredOptions): <TResult = TDefault>(...args: CbArgs) => IPromisE<TResult>;
323
+
324
+ /**
325
+ * @function PromisE.delay
326
+ * @summary Creates a promise that completes after given delay/duration.
327
+ *
328
+ * @param {Number} duration duration in milliseconds
329
+ * @param {unknown} result (optional) specify a value to resolve or reject with.
330
+ * Default: `delayMs` when resolved or timed out error when rejected
331
+ * @param {boolean} asRejected (optional) if `true`, will reject the promise after the delay.
332
+ *
333
+ * @returns See {@link IPromisE_Delay}
334
+ *
335
+ * @example Delay before continuing execution
336
+ * ```typescript
337
+ * import PromisE from '@superutils/promise'
338
+ *
339
+ * console.log('Waiting for app initialization or something else to be ready')
340
+ * // wait 3 seconds before proceeding
341
+ * await PromisE.delay(3000)
342
+ * console.log('App ready')
343
+ * ```
344
+ *
345
+ * @example Execute a function after delay.
346
+ * An awaitable `setTimeout()`.
347
+ * ```typescript
348
+ * import PromisE from '@superutils/promise'
349
+ *
350
+ * PromisE.delay(1000, () => console.log('Prints after 1 second delay'))
351
+ * ```
352
+ */
353
+ declare function delay<T = number, TReject extends boolean = boolean>(duration?: number, result?: T | (() => T), asRejected?: TReject): IPromisE_Delay<T>;
354
+
355
+ /**
356
+ * @function PromisE.delayReject
357
+ * @summary Creates a promise that rejects after given delay/duration.
358
+ *
359
+ * @example Create a promise that will rejectafter 3 seconds
360
+ * ```typescript
361
+ * const rejectPromise = PromisE.delayReject(
362
+ * 3000, // duration in milliseconds
363
+ * new Error('App did not initialization on time'), // reason to reject with
364
+ * )
365
+ * await rejectPromise // throws error message after 3 seconds
366
+ * codeThatWillNotExecute()
367
+ * ```
368
+ *
369
+ * @example Prevent automated promise rejection by forcing it to resolve before timeout
370
+ * ```typescript
371
+ *
372
+ * const rejectPromise = PromisE.delayReject<string>(
373
+ * 3000,
374
+ * new Error('App did not initialization on time'),
375
+ * )
376
+ * let count = 0
377
+ * const appReady = () => ++count >= 2 // return true on second call
378
+ * const intervalId = setInterval(() => {
379
+ * if (!appReady()) return
380
+ * rejectPromise.resolve('force resolves rejectPromise and execution continues')
381
+ * clearInterval(intervalId)
382
+ * }, 100)
383
+ * await rejectPromise
384
+ * console.log('App is now ready')
385
+ * ```
386
+ */
387
+ declare function delayReject<T = never>(duration: number, reason?: unknown): IPromisE_Delay<T>;
388
+
389
+ declare class PromisEBase<T = unknown> extends Promise<T> implements IPromisE<T> {
390
+ readonly state: 0 | 1 | 2;
391
+ private _resolve?;
392
+ private _reject?;
393
+ /**
394
+ * callbacks to be invoked whenever PromisE instance is finalized early using non-static resolve()/reject() methods */
395
+ onEarlyFinalize: OnEarlyFinalize<T>[];
396
+ /** Create a PromisE instance as a drop-in replacement for Promise */
397
+ constructor(...args: PromiseParams<T>);
398
+ /** Extend an existing Promise instance to check status or finalize early */
399
+ constructor(promise: Promise<T>);
400
+ /** Create a resolved promise with value */
401
+ constructor(value: T);
402
+ /**
403
+ * If executor function is not provided, the promise must be resolved/rejected externally.
404
+ *
405
+ * @example An alternative to "Promise.withResolvers()"
406
+ * ```typescript
407
+ * // create a promise that will NEVER finalize automatically
408
+ * const p = new PromisE<number>()
409
+ * // resolve it manually
410
+ * setTimeout(() => p.resolve(1), 1000)
411
+ * p.then(console.log)
412
+ * ```
413
+ */
414
+ constructor();
415
+ /** Indicates if the promise is still pending/unfinalized */
416
+ get pending(): boolean;
417
+ /** Indicates if the promise has been rejected */
418
+ get rejected(): boolean;
419
+ /** Indicates if the promise has been resolved */
420
+ get resolved(): boolean;
421
+ /** Resovle pending promise early. */
422
+ resolve: (value: T | PromiseLike<T>) => void;
423
+ /** Reject pending promise early. */
424
+ reject: (reason: unknown) => void;
425
+ /** Sugar for `new PromisE(Promise.all(...))` */
426
+ static all: <T_1 extends readonly unknown[] | []>(values: T_1) => IPromisE<{ -readonly [P in keyof T_1]: Awaited<T_1[P]>; }>;
427
+ /** Sugar for `new PromisE(Promise.allSettled(...))` */
428
+ static allSettled: <T_1 extends unknown[]>(values: T_1) => IPromisE<PromiseSettledResult<Awaited<T_1[number]>>[]>;
429
+ /** Sugar for `new PromisE(Promise.any(...))` */
430
+ static any: <T_1 extends unknown[]>(values: T_1) => IPromisE<T_1[number]>;
431
+ /** Sugar for `new PromisE(Promise.race(..))` */
432
+ static race: <T_1>(values: T_1[]) => IPromisE<Awaited<T_1>>;
433
+ /** Extends Promise.reject */
434
+ static reject: <T_1 = never>(reason: unknown) => IPromisE<T_1>;
435
+ /** Sugar for `new PromisE(Promise.resolve(...))` */
436
+ static resolve: <T_1>(value?: T_1 | PromiseLike<T_1>) => IPromisE<T_1>;
437
+ /** Sugar for `new PromisE(Promise.try(...))` */
438
+ static try: <T_1, U extends unknown[]>(callbackFn: (...args: U) => T_1 | PromiseLike<T_1>, ...args: U) => IPromisE<Awaited<T_1>>;
439
+ /**
440
+ * Creates a `PromisE` instance and returns it in an object, along with its `resolve` and `reject` functions.
441
+ *
442
+ * NB: this function is technically no longer needed because the `PromisE` class already comes with the resolvers.
443
+ *
444
+ * ---
445
+ * @example
446
+ * Using `PromisE` directly: simply provide an empty function as the executor
447
+ *
448
+ * ```typescript
449
+ * import PromisE from '@superutils/promise'
450
+ * const promisE = new PromisE<number>(() => {})
451
+ * setTimeout(() => promisE.resolve(1), 1000)
452
+ * promisE.then(console.log)
453
+ * ```
454
+ *
455
+ * @example
456
+ * Using `withResolvers`
457
+ * ```typescript
458
+ * import PromisE from '@superutils/promise'
459
+ * const pwr = PromisE.withResolvers<number>()
460
+ * setTimeout(() => pwr.resolve(1), 1000)
461
+ * pwr.promise.then(console.log)
462
+ * ```
463
+ */
464
+ static withResolvers: <T_1 = unknown>() => {
465
+ promise: IPromisE<T_1>;
466
+ reject: (reason: unknown) => void;
467
+ resolve: (value: T_1 | PromiseLike<T_1>) => void;
468
+ };
469
+ }
470
+
471
+ /**
472
+ * @function PromisE.timeout
473
+ * @summary times out a promise after specified timeout duration.
474
+ *
475
+ * @param timeout (optional) timeout duration in milliseconds.
476
+ * Default: `10000` (10 seconds)
477
+ * @param values promise/function: one or more promises as individual arguments
478
+ *
479
+ * @example Example 1: single promise - resolved
480
+ * ```typescript
481
+ * PromisE.timeout(
482
+ * 5000, // timeout after 5000ms
483
+ * PromisE.delay(1000), // resolves after 1000ms with value 1000
484
+ * ).then(console.log)
485
+ * // Result: 1000
486
+ * ```
487
+ *
488
+ * @example Example 2: multiple promises - resolved
489
+ *
490
+ * ```typescript
491
+ * PromisE.timeout(
492
+ * 5000, // timeout after 5000ms
493
+ * PromisE.delay(1000), // resolves after 1000ms with value 1000
494
+ * PromisE.delay(2000), // resolves after 2000ms with value 2000
495
+ * PromisE.delay(3000), // resolves after 3000ms with value 3000
496
+ * ).then(console.log)
497
+ * // Result: [ 1000, 2000, 3000 ]
498
+ * ```
499
+ *
500
+ * @example Example 3: timed out & rejected
501
+ * ```typescript
502
+ * PromisE.timeout(
503
+ * 5000, // timeout after 5000ms
504
+ * PromisE.delay(20000), // resolves after 20000ms with value 20000
505
+ * ).catch(console.error)
506
+ * // Error: Error('Timed out after 5000ms')
507
+ *```
508
+ *
509
+ * @example Example 4: timed out & but not rejected.
510
+ * // Eg: when API request is taking longer than expected, print a message but not reject the promise.
511
+ * ```typescript
512
+ * const promise = PromisE.timeout(
513
+ * 5000, // timeout after 5000ms
514
+ * PromisE.delay(20000), // data promise, resolves after 20000ms with value 20000
515
+ * )
516
+ * const data = await promise.catch(err => {
517
+ * // promise did not time out, but was rejected because one of the data promises rejected
518
+ * if (!promise.timedout) return Promise.reject(err)
519
+ *
520
+ * // promise timed out >> print/update UI
521
+ * console.log('Request is taking longer than expected......')
522
+ * // now return the data promise (the promise(s) provided in the PromisE.timeout())
523
+ * return promise.data
524
+ * })
525
+ *```
526
+ */
527
+ declare function timeout<T extends unknown[] | [], TOut = T['length'] extends 1 ? T[0] : T>(timeout?: number, ...values: Promise<TOut>[]): IPromisE_Timeout<TOut>;
528
+
529
+ /**
530
+ * An attempt to solve the problem of Promise status (pending/resolved/rejected) not being easily accessible externally.
531
+ *
532
+ * For more example see static functions like `PromisE.deferred}, `PromisE.fetch}, `PromisE.timeout} etc.
533
+ *
534
+ *
535
+ * @example Example 1: As a drop-in replacement for Promise class
536
+ * ```typescript
537
+ * import PromisE from '@superutils/promise'
538
+ * const p = new PromisE((resolve, reject) => resolve('done'))
539
+ * console.log(
540
+ * p.pending, // Indicates if promise has finalized (resolved/rejected)
541
+ * p.resolved, // Indicates if the promise has resolved
542
+ * p.rejected // Indicates if the promise has rejected
543
+ * )
544
+ * ```
545
+ *
546
+ * @example Example 2: Extend an existing "Proimse" instance to check status
547
+ * ```typescript
548
+ * import PromisE from '@superutils/promise'
549
+ * const instance = new Promise((resolve) => setTimeout(() => resolve(1), 1000))
550
+ * const p = new PromisE(instance)
551
+ * console.log(p.pending)
552
+ * ```
553
+ *
554
+ * @example Example 3: Create a promise to be finalized externally (an alternative to "PromisE.withResolvers()")
555
+ * ```typescript
556
+ * import PromisE from '@superutils/promise'
557
+ * const p = new PromisE<number>()
558
+ * setTimeout(() => p.resolve(1))
559
+ * p.then(console.log)
560
+ * ```
561
+ *
562
+ * @example Example 4. Invoke functions catching any error and wrapping the result in a PromisE instance
563
+ * ```typescript
564
+ * import PromisE from '@superutils/promise'
565
+ * const p = PromisE.try(() => { throw new Error('I am a naughty function' ) })
566
+ * p.catch(console.error)
567
+ * ```
568
+ */
569
+ declare class PromisE<T = unknown> extends PromisEBase<T> {
570
+ /** Global configuration & default values */
571
+ static config: {
572
+ deferOptions: DeferredOptions;
573
+ delayTimeoutMsg: string;
574
+ retryOptions: {
575
+ retry: number;
576
+ retryBackOff: "exponential";
577
+ retryDelay: number;
578
+ retryDelayJitter: true;
579
+ retryDelayJitterMax: number;
580
+ retryIf: null;
581
+ };
582
+ };
583
+ static deferred: typeof deferred;
584
+ static deferredCallback: typeof deferredCallback;
585
+ static delay: typeof delay;
586
+ static delayReject: typeof delayReject;
587
+ static retry: <T_1>(func: () => _superutils_core.ValueOrPromise<T_1>, options?: RetryOptions<T_1>) => Promise<T_1>;
588
+ static timeout: typeof timeout;
589
+ }
590
+
591
+ /**
592
+ * Executes a function and retries it on failure or until a specific condition is met.
593
+ *
594
+ * The function will be re-executed if:
595
+ * 1. The `func` promise rejects or the function throws an error.
596
+ * 2. The optional `retryIf` function returns `true`.
597
+ * 3. `retry > 0`
598
+ *
599
+ * Retries will stop when the `retry` count is exhausted, or when `func` executes successfully
600
+ * (resolves without error) AND the `retryIf` (if provided) returns `false`.
601
+ *
602
+ * @template T The type of the value that the `func` returns/resolves to.
603
+ * @param {() => ValueOrPromise<T>} func The function to execute. It can be synchronous or asynchronous.
604
+ * @param {RetryOptions} [options={}] (optional) Options for configuring the retry mechanism.
605
+ * @property {number} [options.retry=1] (optional) The maximum number of retries.
606
+ * @property {number} [options.retryDelayMs=300] The base delay in milliseconds between retries.
607
+ * @property {'exponential' | 'fixed'} [options.retryBackOff='exponential'] The backoff strategy. 'exponential' doubles the delay for each subsequent retry. 'fixed' uses a constant delay.
608
+ * @property {boolean} [options.retryDelayJitter=true] If true, adds a random jitter to the delay to prevent thundering herd problem.
609
+ * @property {number} [options.retryDelayJitterMax=100] The maximum jitter in milliseconds to add to the delay.
610
+ * @property {(result: T | undefined, retryCount: number) => boolean} [options.retryIf] A function that is called after a successful execution of `func`. If it returns `true`, a retry is triggered. It receives the result and the current retry count.
611
+ * @returns {Promise<T | undefined>} A promise that resolves with the result of the last successful execution of `func`.
612
+ * If all retries fail (either by throwing an error or by the condition function always returning true),
613
+ * it resolves with `undefined`. Errors thrown by `func` are caught and handled internally, not re-thrown.
614
+ */
615
+ declare const retry: <T>(func: () => ValueOrPromise<T>, options?: RetryOptions<T>) => Promise<T>;
616
+
617
+ export { type Config, type DeferredOptions, type DeferredReturn, type IPromisE, type IPromisE_Delay, type IPromisE_Timeout, type OnEarlyFinalize, PromisE, PromisEBase, type PromiseParams, ResolveError, ResolveIgnored, type RetryOptions, config, PromisE as default, deferred, deferredCallback, delay, delayReject, retry, timeout };
package/dist/index.js ADDED
@@ -0,0 +1,408 @@
1
+ // src/types/deferred.ts
2
+ var ResolveError = /* @__PURE__ */ ((ResolveError2) => {
3
+ ResolveError2["NEVER"] = "NEVER";
4
+ ResolveError2["REJECT"] = "REJECT";
5
+ ResolveError2["WITH_ERROR"] = "RESOLVE_ERROR";
6
+ ResolveError2["WITH_UNDEFINED"] = "RESOLVE_UNDEFINED";
7
+ return ResolveError2;
8
+ })(ResolveError || {});
9
+ var ResolveIgnored = /* @__PURE__ */ ((ResolveIgnored2) => {
10
+ ResolveIgnored2["NEVER"] = "NEVER";
11
+ ResolveIgnored2["WITH_LAST"] = "WITH_LAST";
12
+ ResolveIgnored2["WITH_UNDEFINED"] = "WITH_UNDEFINED";
13
+ return ResolveIgnored2;
14
+ })(ResolveIgnored || {});
15
+
16
+ // src/config.ts
17
+ var config = {
18
+ /** Default value for `options` used by `PromisE.*deferred*` functions */
19
+ deferOptions: {
20
+ delayMs: 100,
21
+ resolveError: "REJECT" /* REJECT */,
22
+ resolveIgnored: "WITH_LAST" /* WITH_LAST */,
23
+ throttle: false
24
+ },
25
+ delayTimeoutMsg: "Timed out after",
26
+ retryOptions: {
27
+ retry: 1,
28
+ retryBackOff: "exponential",
29
+ retryDelay: 300,
30
+ retryDelayJitter: true,
31
+ retryDelayJitterMax: 100,
32
+ retryIf: null
33
+ }
34
+ };
35
+ var config_default = config;
36
+
37
+ // src/deferred.ts
38
+ import {
39
+ deferred as deferredCore,
40
+ fallbackIfFails as fallbackIfFails2,
41
+ forceCast,
42
+ isFn as isFn2,
43
+ isPositiveNumber,
44
+ throttled as throttledCore
45
+ } from "@superutils/core";
46
+
47
+ // src/PromisEBase.ts
48
+ import { asAny, fallbackIfFails, isFn, isPromise } from "@superutils/core";
49
+ var _PromisEBase = class _PromisEBase extends Promise {
50
+ constructor(input) {
51
+ if (input instanceof _PromisEBase) return input;
52
+ let _resolve;
53
+ let _reject;
54
+ super((resolve, reject) => {
55
+ _reject = (reason) => {
56
+ asAny(this).state = 2;
57
+ reject(reason);
58
+ };
59
+ _resolve = (value) => {
60
+ asAny(this).state = 1;
61
+ resolve(value);
62
+ };
63
+ input != null ? input : input = () => {
64
+ };
65
+ const promise = isPromise(input) ? input : isFn(input) ? new globalThis.Promise(input) : Promise.resolve(input);
66
+ promise.then(_resolve, _reject);
67
+ });
68
+ this.state = 0;
69
+ /**
70
+ * callbacks to be invoked whenever PromisE instance is finalized early using non-static resolve()/reject() methods */
71
+ this.onEarlyFinalize = [];
72
+ //
73
+ //
74
+ // --------------------------- Early resolve/reject ---------------------------
75
+ //
76
+ //
77
+ /** Resovle pending promise early. */
78
+ this.resolve = (value) => {
79
+ var _a, _b;
80
+ if (!this.pending) return;
81
+ (_a = this._resolve) == null ? void 0 : _a.call(this, value);
82
+ (_b = this.onEarlyFinalize) == null ? void 0 : _b.forEach((fn) => {
83
+ fallbackIfFails(fn, [true, value], void 0);
84
+ });
85
+ };
86
+ /** Reject pending promise early. */
87
+ this.reject = (reason) => {
88
+ var _a, _b;
89
+ if (!this.pending) return;
90
+ (_a = this._reject) == null ? void 0 : _a.call(this, reason);
91
+ (_b = this.onEarlyFinalize) == null ? void 0 : _b.forEach((fn) => {
92
+ fallbackIfFails(fn, [false, reason], void 0);
93
+ });
94
+ };
95
+ this._resolve = _resolve;
96
+ this._reject = _reject;
97
+ }
98
+ //
99
+ //
100
+ //-------------------- Status related read-only attributes --------------------
101
+ //
102
+ //
103
+ /** Indicates if the promise is still pending/unfinalized */
104
+ get pending() {
105
+ return this.state === 0;
106
+ }
107
+ /** Indicates if the promise has been rejected */
108
+ get rejected() {
109
+ return this.state === 2;
110
+ }
111
+ /** Indicates if the promise has been resolved */
112
+ get resolved() {
113
+ return this.state === 1;
114
+ }
115
+ // static withResolvers = <T = unknown>() => {
116
+ // const pwr = globalThis.Promise.withResolvers<T>()
117
+ // const promise = new PromisEBase<T>(pwr.promise) as IPromisE<T>
118
+ // return { ...pwr, promise }
119
+ // }
120
+ };
121
+ //
122
+ //
123
+ // Extend all static `Promise` methods
124
+ //
125
+ //
126
+ /** Sugar for `new PromisE(Promise.all(...))` */
127
+ _PromisEBase.all = (values) => new _PromisEBase(globalThis.Promise.all(values));
128
+ /** Sugar for `new PromisE(Promise.allSettled(...))` */
129
+ _PromisEBase.allSettled = (values) => new _PromisEBase(globalThis.Promise.allSettled(values));
130
+ /** Sugar for `new PromisE(Promise.any(...))` */
131
+ _PromisEBase.any = (values) => new _PromisEBase(globalThis.Promise.any(values));
132
+ /** Sugar for `new PromisE(Promise.race(..))` */
133
+ _PromisEBase.race = (values) => new _PromisEBase(globalThis.Promise.race(values));
134
+ /** Extends Promise.reject */
135
+ _PromisEBase.reject = (reason) => {
136
+ const { promise, reject } = _PromisEBase.withResolvers();
137
+ queueMicrotask(() => reject(reason));
138
+ return promise;
139
+ };
140
+ /** Sugar for `new PromisE(Promise.resolve(...))` */
141
+ _PromisEBase.resolve = (value) => new _PromisEBase(
142
+ globalThis.Promise.resolve(value)
143
+ );
144
+ /** Sugar for `new PromisE(Promise.try(...))` */
145
+ _PromisEBase.try = (callbackFn, ...args) => new _PromisEBase(
146
+ (resolve) => resolve(
147
+ // Promise.try is not supported in Node < 23.
148
+ fallbackIfFails(
149
+ callbackFn,
150
+ args,
151
+ // rethrow error to ensure the returned promise is rejected
152
+ (err) => globalThis.Promise.reject(err)
153
+ )
154
+ )
155
+ );
156
+ /**
157
+ * Creates a `PromisE` instance and returns it in an object, along with its `resolve` and `reject` functions.
158
+ *
159
+ * NB: this function is technically no longer needed because the `PromisE` class already comes with the resolvers.
160
+ *
161
+ * ---
162
+ * @example
163
+ * Using `PromisE` directly: simply provide an empty function as the executor
164
+ *
165
+ * ```typescript
166
+ * import PromisE from '@superutils/promise'
167
+ * const promisE = new PromisE<number>(() => {})
168
+ * setTimeout(() => promisE.resolve(1), 1000)
169
+ * promisE.then(console.log)
170
+ * ```
171
+ *
172
+ * @example
173
+ * Using `withResolvers`
174
+ * ```typescript
175
+ * import PromisE from '@superutils/promise'
176
+ * const pwr = PromisE.withResolvers<number>()
177
+ * setTimeout(() => pwr.resolve(1), 1000)
178
+ * pwr.promise.then(console.log)
179
+ * ```
180
+ */
181
+ _PromisEBase.withResolvers = () => {
182
+ const promise = new _PromisEBase();
183
+ return { promise, reject: promise.reject, resolve: promise.resolve };
184
+ };
185
+ var PromisEBase = _PromisEBase;
186
+ var PromisEBase_default = PromisEBase;
187
+
188
+ // src/deferred.ts
189
+ function deferred(options = {}) {
190
+ const defaults = config_default.deferOptions;
191
+ let { onError, onIgnore, onResult } = options;
192
+ const {
193
+ delayMs = defaults.delayMs,
194
+ resolveError = defaults.resolveError,
195
+ // by default reject on error
196
+ resolveIgnored = defaults.resolveIgnored,
197
+ thisArg,
198
+ throttle = defaults.throttle
199
+ } = options;
200
+ let lastPromisE = null;
201
+ const queue = /* @__PURE__ */ new Map();
202
+ const gotDelay = isPositiveNumber(delayMs);
203
+ if (thisArg !== void 0) {
204
+ onError = onError == null ? void 0 : onError.bind(thisArg);
205
+ onIgnore = onIgnore == null ? void 0 : onIgnore.bind(thisArg);
206
+ onResult = onResult == null ? void 0 : onResult.bind(thisArg);
207
+ }
208
+ const ignoreOrProceed = (currentId, qItem) => {
209
+ lastPromisE = null;
210
+ if (!gotDelay) {
211
+ queue.delete(currentId);
212
+ const [nextId, nextItem] = [...queue.entries()][0] || [];
213
+ return nextId && nextItem && execute(nextId, nextItem);
214
+ }
215
+ const items = [...queue.entries()];
216
+ const currentIndex = items.findIndex(([id]) => id === currentId);
217
+ for (let i = 0; i <= currentIndex; i++) {
218
+ const [iId, iItem] = items[i];
219
+ queue.delete(iId);
220
+ if (iItem == void 0 || iItem.started) continue;
221
+ onIgnore && fallbackIfFails2(onIgnore, [iItem.getPromise], void 0);
222
+ switch (resolveIgnored) {
223
+ case "WITH_UNDEFINED" /* WITH_UNDEFINED */:
224
+ iItem.resolve(void 0);
225
+ break;
226
+ case "WITH_LAST" /* WITH_LAST */:
227
+ qItem == null ? void 0 : qItem.then(iItem.resolve, iItem.reject);
228
+ break;
229
+ case "NEVER" /* NEVER */:
230
+ break;
231
+ }
232
+ }
233
+ };
234
+ const finalizeCb = (resolve, id, qItem) => (resultOrErr) => {
235
+ ignoreOrProceed(id, qItem);
236
+ if (resolve) {
237
+ qItem.resolve(resultOrErr);
238
+ onResult && fallbackIfFails2(onResult, [resultOrErr], void 0);
239
+ return;
240
+ }
241
+ onError && fallbackIfFails2(onError, [resultOrErr], void 0);
242
+ switch (resolveError) {
243
+ case "REJECT" /* REJECT */:
244
+ qItem.reject(resultOrErr);
245
+ // eslint-disable-next-line no-fallthrough
246
+ case "NEVER" /* NEVER */:
247
+ break;
248
+ case "RESOLVE_UNDEFINED" /* WITH_UNDEFINED */:
249
+ resultOrErr = void 0;
250
+ // eslint-disable-next-line no-fallthrough
251
+ case "RESOLVE_ERROR" /* WITH_ERROR */:
252
+ qItem.resolve(resultOrErr);
253
+ break;
254
+ }
255
+ };
256
+ const execute = (() => {
257
+ const execute2 = (id, qItem) => {
258
+ qItem.started = true;
259
+ lastPromisE = new PromisEBase_default(qItem.getPromise());
260
+ lastPromisE.then(
261
+ finalizeCb(true, id, qItem),
262
+ finalizeCb(false, id, qItem)
263
+ );
264
+ };
265
+ if (!gotDelay) return execute2;
266
+ const deferFn = throttle ? throttledCore : deferredCore;
267
+ return deferFn(execute2, delayMs, options);
268
+ })();
269
+ const deferredFunc = (promise) => {
270
+ const id = Symbol("deferred-queue-item-id");
271
+ const qItem = new PromisEBase_default();
272
+ qItem.getPromise = isFn2(promise) ? promise : () => promise;
273
+ qItem.started = false;
274
+ queue.set(id, qItem);
275
+ if (gotDelay || !lastPromisE) execute(id, qItem);
276
+ return forceCast(qItem);
277
+ };
278
+ return deferredFunc;
279
+ }
280
+ var deferred_default = deferred;
281
+
282
+ // src/deferredCallback.ts
283
+ function deferredCallback(callback, options = {}) {
284
+ const { thisArg } = options;
285
+ if (thisArg !== void 0) callback = callback.bind(thisArg);
286
+ const deferPromise = deferred_default(options);
287
+ return (...args) => deferPromise(() => callback(...args));
288
+ }
289
+ var deferredCallback_default = deferredCallback;
290
+
291
+ // src/delay.ts
292
+ import { fallbackIfFails as fallbackIfFails3, isFn as isFn3 } from "@superutils/core";
293
+ function delay(duration = 100, result = duration, asRejected = false) {
294
+ const promise = new PromisEBase_default();
295
+ const finalize = (result2) => {
296
+ var _a;
297
+ if (isFn3(result2))
298
+ result2 = (_a = fallbackIfFails3(result2, [], void 0)) != null ? _a : duration;
299
+ if (!asRejected) return promise.resolve(result2);
300
+ promise.reject(
301
+ result2 != null ? result2 : new Error(`${config_default.delayTimeoutMsg} ${duration}ms`)
302
+ );
303
+ };
304
+ promise.timeoutId = setTimeout(() => finalize(result), duration);
305
+ promise.pause = () => clearTimeout(promise.timeoutId);
306
+ promise.catch(() => {
307
+ }).finally(() => promise.pause());
308
+ return promise;
309
+ }
310
+ var delay_default = delay;
311
+
312
+ // src/delayReject.ts
313
+ function delayReject(duration, reason) {
314
+ return delay_default(duration, reason, true);
315
+ }
316
+ var delayReject_default = delayReject;
317
+
318
+ // src/retry.ts
319
+ import { isPositiveInteger } from "@superutils/core";
320
+ var retry = async (func, options = {}) => {
321
+ const d = config_default.retryOptions;
322
+ const {
323
+ retryIf,
324
+ retryBackOff = d.retryBackOff,
325
+ retryDelayJitter = d.retryDelayJitter
326
+ } = options;
327
+ let {
328
+ retry: maxRetries = 1,
329
+ retryDelay: delayMs,
330
+ retryDelayJitterMax: jitterMax
331
+ } = options;
332
+ maxRetries = maxRetries >= 0 ? maxRetries : d.retry;
333
+ delayMs = isPositiveInteger(delayMs) ? delayMs : d.retryDelay;
334
+ jitterMax = isPositiveInteger(jitterMax) ? jitterMax : d.retryDelayJitterMax;
335
+ let retryCount = -1;
336
+ let result;
337
+ let error;
338
+ let shouldRetry = false;
339
+ do {
340
+ retryCount++;
341
+ if (retryBackOff === "exponential" && retryCount > 1) delayMs *= 2;
342
+ if (retryDelayJitter) delayMs += Math.floor(Math.random() * jitterMax);
343
+ retryCount > 0 && await delay_default(delayMs);
344
+ try {
345
+ error = void 0;
346
+ result = await func();
347
+ } catch (err) {
348
+ error = err;
349
+ }
350
+ shouldRetry = maxRetries > 0 && (!!error || !!(retryIf == null ? void 0 : retryIf(result, retryCount, error))) && retryCount < maxRetries;
351
+ } while (shouldRetry);
352
+ if (error !== void 0) return Promise.reject(error);
353
+ return result;
354
+ };
355
+ var retry_default = retry;
356
+
357
+ // src/timeout.ts
358
+ function timeout(timeout2 = 1e4, ...values) {
359
+ const dataPromise = values.length === 1 ? new PromisEBase_default(values[0]) : PromisEBase_default.all(values);
360
+ const timeoutPromise = delayReject_default(
361
+ timeout2,
362
+ new Error(`Timed out after ${timeout2}ms`)
363
+ );
364
+ const promise = PromisEBase_default.race([
365
+ dataPromise,
366
+ timeoutPromise
367
+ ]);
368
+ promise.clearTimeout = () => clearTimeout(timeoutPromise.timeoutId);
369
+ promise.data = dataPromise;
370
+ promise.timeout = timeoutPromise;
371
+ Object.defineProperty(promise, "timedout", {
372
+ get: () => promise.timeout.rejected
373
+ });
374
+ dataPromise.catch(() => {
375
+ }).finally(promise.clearTimeout);
376
+ return promise;
377
+ }
378
+ var timeout_default = timeout;
379
+
380
+ // src/PromisE.ts
381
+ var PromisE = class extends PromisEBase_default {
382
+ };
383
+ /** Global configuration & default values */
384
+ PromisE.config = config_default;
385
+ PromisE.deferred = deferred_default;
386
+ PromisE.deferredCallback = deferredCallback_default;
387
+ PromisE.delay = delay_default;
388
+ PromisE.delayReject = delayReject_default;
389
+ PromisE.retry = retry_default;
390
+ PromisE.timeout = timeout_default;
391
+ var PromisE_default = PromisE;
392
+
393
+ // src/index.ts
394
+ var index_default = PromisE_default;
395
+ export {
396
+ PromisE,
397
+ PromisEBase,
398
+ ResolveError,
399
+ ResolveIgnored,
400
+ config,
401
+ index_default as default,
402
+ deferred,
403
+ deferredCallback,
404
+ delay,
405
+ delayReject,
406
+ retry,
407
+ timeout
408
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "author": "Toufiqur Rahaman Chowdhury",
3
+ "bugs": {
4
+ "url": "https://github.com/alien45/superutils/issues"
5
+ },
6
+ "dependencies": {
7
+ "@superutils/core": "^1.0.1"
8
+ },
9
+ "description": "An extended Promise class with extra features and utilities.",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "homepage": "https://github.com/alien45/superutils/#readme",
16
+ "keywords": [
17
+ "promise",
18
+ "async",
19
+ "util",
20
+ "typescript"
21
+ ],
22
+ "license": "MIT",
23
+ "main": "dist/index.js",
24
+ "name": "@superutils/promise",
25
+ "peerDpendencies": {
26
+ "@superutils/core": "^1.0.1"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/alien45/superutils.git"
34
+ },
35
+ "scripts": {
36
+ "_build": "tsc -p tsconfig.json",
37
+ "_watch": "tsc -p tsconfig.json --watch",
38
+ "build": "tsup src/index.ts --format esm --dts --clean --config ../../tsup.config.js",
39
+ "dev": "npm run build -- --watch",
40
+ "test": "vitest"
41
+ },
42
+ "sideEffects": false,
43
+ "type": "module",
44
+ "types": "dist/index.d.ts",
45
+ "version": "1.0.1"
46
+ }