@superutils/promise 1.1.4 → 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 +75 -35
- package/dist/index.d.ts +46 -25
- package/dist/index.js +49 -40
- package/package.json +4 -4
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
|
|
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
|
-
|
|
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/
|
|
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
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
```
|
|
79
|
-
import
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
```
|
|
148
|
+
```javascript
|
|
127
149
|
import PromisE from '@superutils/promise'
|
|
128
150
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
```
|
|
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
|
|
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
|
|
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
|
-
|
|
242
|
-
|
|
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,
|
|
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
|
|
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>>, '
|
|
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:
|
|
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
|
-
*
|
|
183
|
+
* Minimum delay in milliseconds between retries.
|
|
183
184
|
* Default: `300`
|
|
184
185
|
*/
|
|
185
186
|
retryDelay?: number;
|
|
186
187
|
/**
|
|
187
|
-
*
|
|
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
|
|
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
|
|
198
|
-
*
|
|
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?:
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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.
|
|
70
|
+
return this.state === 0;
|
|
70
71
|
}
|
|
71
72
|
/** Indicates if the promise has been rejected */
|
|
72
73
|
get rejected() {
|
|
73
|
-
return this.
|
|
74
|
+
return this.state === 2;
|
|
74
75
|
}
|
|
75
76
|
/** Indicates if the promise has been resolved */
|
|
76
77
|
get resolved() {
|
|
77
|
-
return this.
|
|
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
|
|
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
|
|
205
|
+
const handleIgnore = (items) => {
|
|
203
206
|
for (const [iId, iItem] of items) {
|
|
204
207
|
queue.delete(iId);
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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 : (
|
|
274
|
+
const handleItem = isSequential ? executeItem : deferredSync(
|
|
269
275
|
executeItem,
|
|
270
276
|
delayMs,
|
|
271
277
|
options
|
|
272
278
|
);
|
|
273
|
-
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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.
|
|
7
|
+
"@superutils/core": "^1.1.6"
|
|
8
8
|
},
|
|
9
|
-
"description": "An extended Promise with
|
|
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.
|
|
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.
|
|
46
|
+
"version": "1.1.6"
|
|
47
47
|
}
|