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