@superutils/promise 1.1.5 → 1.1.6

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, TimeoutId, PositiveNumber, DeferredOptions } from '@superutils/core';
3
3
 
4
4
  interface IPromisE<T = unknown> extends Promise<T> {
5
5
  /** 0: pending, 1: resolved, 2: rejected */
@@ -79,9 +79,11 @@ type PromiseParams<T = unknown> = ConstructorParameters<typeof Promise<T>>;
79
79
 
80
80
  /** Return type of `PromisE.deferred()` */
81
81
  type DeferredAsyncCallback<TArgs extends unknown[] | [] = []> = <TResult = unknown>(promise: Promise<TResult> | ((...args: TArgs) => Promise<TResult>)) => IPromisE<TResult>;
82
- type DeferredAsyncGetPromise<T> = <TResult = T>() => Promise<TResult>;
82
+ type GetPromiseFunc<T> = <TResult = T>() => Promise<TResult>;
83
83
  /** Default options used by `PromisE.deferred` and related functions */
84
- type DeferredAsyncDefaults<ThisArg = unknown, Delay = unknown> = Pick<Required<DeferredAsyncOptions<ThisArg, Delay>>, 'delayMs' | 'resolveError' | 'resolveIgnored'>;
84
+ type DeferredAsyncDefaults<ThisArg = unknown, Delay = unknown> = Pick<Required<DeferredAsyncOptions<ThisArg, Delay>>, 'resolveError' | 'resolveIgnored'> & {
85
+ delayMs: number;
86
+ };
85
87
  /** Options for `PromisE.deferred` and other related functions */
86
88
  type DeferredAsyncOptions<ThisArg = unknown, Delay = unknown> = {
87
89
  /**
@@ -100,14 +102,12 @@ type DeferredAsyncOptions<ThisArg = unknown, Delay = unknown> = {
100
102
  * Default: `false`
101
103
  */
102
104
  ignoreStale?: boolean;
103
- /** Callback invoked whenever promise/function throws error */
104
- onError?: (this: ThisArg, err: unknown) => ValueOrPromise<unknown>;
105
105
  /**
106
106
  * Whenever a promise/function is ignored when in debounce/throttle mode, `onIgnored` wil be invoked.
107
107
  * The promise/function will not be invoked, unless it's manually invoked using the `ignored` function.
108
108
  * Use for debugging or logging purposes.
109
109
  */
110
- onIgnore?: (this: ThisArg, ignored: DeferredAsyncGetPromise<unknown>) => ValueOrPromise<unknown>;
110
+ onIgnore?: (this: ThisArg, ignored: GetPromiseFunc<unknown>) => ValueOrPromise<unknown>;
111
111
  /**
112
112
  * Whenever a promise/function is executed successfully `onResult` will be called.
113
113
  * Those that are ignored but resolve with last will not cause `onResult` to be invoked.
@@ -125,18 +125,17 @@ type DeferredAsyncOptions<ThisArg = unknown, Delay = unknown> = {
125
125
  * See {@link ResolveError} for available options.
126
126
  */
127
127
  resolveError?: ResolveError;
128
- /** The value to be used as "thisArg" whenever any of the callbacks are invoked */
129
- thisArg?: ThisArg;
130
128
  } & (({
131
- delayMs: PositiveNumber<Delay>;
132
- throttle: true;
133
- } & Pick<ThrottleOptions, 'trailing'>) | ({
134
- delayMs?: PositiveNumber<Delay>;
135
- throttle?: false;
136
- } & Pick<DeferredOptions, 'leading'>) | {
137
129
  delayMs: 0;
130
+ } & {
138
131
  throttle?: false;
139
- });
132
+ /** Callback invoked whenever promise/function throws error */
133
+ onError?: (this: ThisArg, err: unknown) => ValueOrPromise<unknown>;
134
+ /** The value to be used as "thisArg" whenever any of the callbacks are invoked */
135
+ thisArg?: ThisArg;
136
+ }) | ({
137
+ delayMs?: PositiveNumber<Delay>;
138
+ } & DeferredOptions<ThisArg>));
140
139
  /** Determines what to do when deferred promise/function fails */
141
140
  declare enum ResolveError {
142
141
  /** Neither resolve nor reject the failed */
@@ -161,6 +160,8 @@ declare enum ResolveIgnored {
161
160
  WITH_UNDEFINED = "WITH_UNDEFINED"
162
161
  }
163
162
 
163
+ /** Function to determine whether retry should be attempted based on previous result/error */
164
+ type RetryIfFunc<T = unknown> = (prevResult: T | undefined, retryCount: number, error?: unknown) => ValueOrPromise<boolean | void>;
164
165
  /** Options for automatic retry mechanism */
165
166
  type RetryOptions<T = unknown> = {
166
167
  /**
@@ -179,30 +180,47 @@ type RetryOptions<T = unknown> = {
179
180
  */
180
181
  retryBackOff?: 'exponential' | 'linear';
181
182
  /**
182
- * Delay in milliseconds between retries.
183
+ * Minimum delay in milliseconds between retries.
183
184
  * Default: `300`
184
185
  */
185
186
  retryDelay?: number;
186
187
  /**
187
- * Add a random delay between 0ms and `retryDelayJitterMax` to the `retryDelay`.
188
+ * Whether to add a random delay between 0ms and `retryDelayJitterMax` to the `retryDelay`.
189
+ *
190
+ * This helps to avoid "thundering herd" problem when multiple retries are attempted simultaneously.
191
+ *
188
192
  * Default: `true`
189
193
  */
190
194
  retryDelayJitter?: boolean;
191
195
  /**
192
- * Maximum delay (in milliseconds) to be used when randomly generating jitter delay duration.
196
+ * Maximum jitter delay (in milliseconds) to be added to the `retryDelay` when `retryDelayJitter` is `true`.
197
+ *
198
+ * A random value between `0` and `retryDelayJitterMax` will be added to the base `retryDelay`.
199
+ *
193
200
  * Default: `100`
194
201
  */
195
202
  retryDelayJitterMax?: number;
196
203
  /**
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.
204
+ * Additional condition to be used to determine whether function should be retried.
205
+ *
206
+ * If `retryIf` not provided, execution will be retried on any error until the retry limit is reached
207
+ * or a result is received without an exception.
208
+ *
209
+ * @param prevResult The result from the previous attempt, if any.
210
+ * @param retryCount The number of retries that have been attempted so far.
211
+ * @param error The error from the previous attempt, if any.
212
+ *
213
+ * Expected return values:
214
+ * - `true`: retry will be attempted regardless of error.
215
+ * - `false`: no further retry will be attempted and the last error/result will be returned.
216
+ * - `void | undefined`: retry will be attempted only if there was an error.
199
217
  */
200
- retryIf?: null | ((prevResult: T | undefined, retryCount: number) => boolean | Promise<boolean>);
218
+ retryIf?: RetryIfFunc<T>;
201
219
  };
202
220
 
203
221
  declare class PromisEBase<T = unknown> extends Promise<T> implements IPromisE<T> {
204
- private _resolve?;
205
- private _reject?;
222
+ private _resolve;
223
+ private _reject;
206
224
  private _state;
207
225
  /**
208
226
  * callbacks to be invoked whenever PromisE instance is finalized early using non-static resolve()/reject() methods */
@@ -213,6 +231,8 @@ declare class PromisEBase<T = unknown> extends Promise<T> implements IPromisE<T>
213
231
  constructor(promise: Promise<T>);
214
232
  /** Create a resolved promise with value */
215
233
  constructor(value: T);
234
+ /** Create a promise to be resolved externally using `.resolve()` and `.reject()` methods */
235
+ constructor(value: undefined);
216
236
  /**
217
237
  * If executor function is not provided, the promise must be resolved/rejected externally.
218
238
  *
@@ -312,7 +332,8 @@ type TimeoutOptions<Func extends string = 'all'> = {
312
332
 
313
333
  /**
314
334
  * @function PromisE.deferred
315
- * The adaptation of the `deferred()` function tailored for Promises.
335
+ *
336
+ * The adaptation of the `deferred()` function from `@superutils/core` tailored for Promises.
316
337
  *
317
338
  *
318
339
  * # Notes
@@ -771,4 +792,4 @@ declare const retry: {
771
792
  };
772
793
  };
773
794
 
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 };
795
+ export { type DeferredAsyncCallback, type DeferredAsyncDefaults, type DeferredAsyncOptions, 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, 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:
@@ -186,12 +187,14 @@ function deferred(options = {}) {
186
187
  let { onError, onIgnore, onResult } = options;
187
188
  const {
188
189
  delayMs = 0,
190
+ ignoreStale,
189
191
  resolveError,
190
192
  resolveIgnored,
191
193
  thisArg,
192
194
  throttle
193
195
  } = options;
194
- let prevQItem = null;
196
+ let lastInSeries = null;
197
+ let lastExecuted;
195
198
  const queue = /* @__PURE__ */ new Map();
196
199
  const isSequential = !isPositiveNumber(delayMs);
197
200
  if (thisArg !== void 0) {
@@ -199,17 +202,21 @@ function deferred(options = {}) {
199
202
  onIgnore = onIgnore == null ? void 0 : onIgnore.bind(thisArg);
200
203
  onResult = onResult == null ? void 0 : onResult.bind(thisArg);
201
204
  }
202
- const handleIgnore = (items, prevQItem2) => {
205
+ const handleIgnore = (items) => {
203
206
  for (const [iId, iItem] of items) {
204
207
  queue.delete(iId);
205
- if (iItem === void 0 || iItem.started) continue;
206
- onIgnore && fallbackIfFails2(onIgnore, [iItem.getPromise], 0);
208
+ const isStale = ignoreStale && iItem.sequence < lastExecuted.sequence;
209
+ if (iItem.resolved || iItem.started && !isStale) continue;
210
+ if (iItem.started) {
211
+ iItem.getPromise = (() => iItem.result);
212
+ }
213
+ fallbackIfFails2(onIgnore, [iItem.getPromise], 0);
207
214
  switch (resolveIgnored) {
208
215
  case "WITH_UNDEFINED" /* WITH_UNDEFINED */:
209
216
  iItem.resolve(void 0);
210
217
  break;
211
218
  case "WITH_LAST" /* WITH_LAST */:
212
- prevQItem2 == null ? void 0 : prevQItem2.then(iItem.resolve, iItem.reject);
219
+ lastExecuted == null ? void 0 : lastExecuted.then(iItem.resolve, iItem.reject);
213
220
  break;
214
221
  case "NEVER" /* NEVER */:
215
222
  break;
@@ -218,33 +225,32 @@ function deferred(options = {}) {
218
225
  if (!queue.size) sequence = 0;
219
226
  };
220
227
  const handleRemaining = (currentId) => {
221
- const _prevQItem = prevQItem;
222
- prevQItem = null;
228
+ lastInSeries = null;
223
229
  if (isSequential) {
224
230
  queue.delete(currentId);
225
231
  const [nextId, nextItem] = [...queue.entries()][0] || [];
226
232
  return nextId && nextItem && handleItem(nextId, nextItem);
227
233
  }
228
234
  let items = [...queue.entries()];
229
- if (throttle && options.trailing) {
235
+ if (throttle === true && options.trailing) {
230
236
  const currentIndex = items.findIndex(([id]) => id === currentId);
231
237
  items = items.slice(0, currentIndex);
232
238
  } else if (!throttle) {
233
239
  items = items.slice(0, -1);
234
240
  }
235
- handleIgnore(items, _prevQItem);
241
+ handleIgnore(items);
242
+ queue.delete(currentId);
236
243
  };
237
- let prevSeq = 0;
238
244
  const executeItem = async (id, qItem) => {
239
245
  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
246
  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);
247
+ qItem.started = true;
248
+ lastExecuted = qItem;
249
+ lastInSeries = qItem;
250
+ (_a = qItem.result) != null ? _a : qItem.result = PromisEBase_default.try(qItem.getPromise);
251
+ const result = await qItem.result;
252
+ const isStale = !!ignoreStale && qItem.sequence < lastExecuted.sequence;
253
+ if (isStale) return handleIgnore([[id, qItem]]);
248
254
  qItem.resolve(result);
249
255
  onResult && fallbackIfFails2(onResult, [result], void 0);
250
256
  } catch (err) {
@@ -265,22 +271,21 @@ function deferred(options = {}) {
265
271
  }
266
272
  handleRemaining(id);
267
273
  };
268
- const handleItem = isSequential ? executeItem : (throttle ? throttledCore : deferredCore)(
274
+ const handleItem = isSequential ? executeItem : deferredSync(
269
275
  executeItem,
270
276
  delayMs,
271
277
  options
272
278
  );
273
- const deferredFunc = (promise) => {
279
+ return (promise) => {
274
280
  const id = /* @__PURE__ */ Symbol("deferred-queue-item-id");
275
281
  const qItem = new PromisEBase_default();
276
282
  qItem.getPromise = isFn2(promise) ? promise : () => promise;
277
283
  qItem.started = false;
278
284
  qItem.sequence = ++sequence;
279
285
  queue.set(id, qItem);
280
- if (!prevQItem || !isSequential) handleItem(id, qItem);
286
+ if (!lastInSeries || !isSequential) handleItem(id, qItem);
281
287
  return qItem;
282
288
  };
283
- return deferredFunc;
284
289
  }
285
290
  deferred.defaults = {
286
291
  /**
@@ -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;
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.6"
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.1.6"
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.1.6"
47
47
  }