@superutils/promise 1.0.9 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -117,7 +117,7 @@ while (!appReady) {
117
117
  }
118
118
  ```
119
119
 
120
- #### `PromisE.delay(duration, callback)`: execute after delay
120
+ #### `PromisE.delay(duration, callback, asRejected)`: execute after delay
121
121
 
122
122
  Creates a promise that executes a function after a specified duration and returns the value the function returns.
123
123
 
@@ -126,10 +126,13 @@ If callback returns undefined, default value will be the duration.
126
126
  ```typescript
127
127
  import PromisE from '@superutils/promise'
128
128
 
129
- const callback = () => {
130
- /* do stuff here */
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')
131
134
  }
132
- await PromisE.delay(100, callback)
135
+ func()
133
136
  ```
134
137
 
135
138
  <div id="deferred"></div>
@@ -138,32 +141,48 @@ await PromisE.delay(100, callback)
138
141
 
139
142
  Create a function that debounces or throttles promise-returning function calls. This is useful for scenarios like auto-saving user input or preventing multiple rapid API calls.
140
143
 
141
- ```typescript
142
- import PromisE, { ResolveIgnored } from '@superutils/promise'
144
+ #### Debounce example:
143
145
 
144
- // Create a deferred function that waits 300ms after the last call
145
- const deferredSave = PromisE.deferred({
146
- defer: 300,
147
- /** ignored promises will resolve with `undefined` */
148
- resolveIgnored: ResolveIgnored.WITH_UNDEFINED,
146
+ ```typescript
147
+ const example = async (options = {}) => {
148
+ const df = PromisE.deferred({
149
+ delayMs: 100,
150
+ resolveIgnored: ResolveIgnored.NEVER, // never resolve ignored calls
151
+ ...options,
152
+ })
153
+ df(() => PromisE.delay(500)).then(console.log)
154
+ df(() => PromisE.delay(1000)).then(console.log)
155
+ df(() => PromisE.delay(5000)).then(console.log)
156
+ // delay 2 seconds and invoke df() again
157
+ await PromisE.delay(2000)
158
+ df(() => PromisE.delay(200)).then(console.log)
159
+ }
160
+ example({ ignoreStale: false, throttle: false })
161
+ // `200` and `1000` will be printed in the console
162
+ example({ ignoreStale: true, throttle: false })
163
+ // `200` will be printed in the console
164
+ ```
149
165
 
150
- /** ignored promises will NEVER be resolved/rejected
151
- * USE WITH CAUTION!
152
- */
153
- resolveIgnored: ResolveIgnored.NEVER,
166
+ #### Throttle example:
154
167
 
155
- // ignored promises will resolve with the result of the last call
156
- resolveIgnored: ResolveIgnored.WITH_LAST, // (default)
157
- })
158
-
159
- // Simulate rapid calls
160
- deferredSave(() => api.save({ text: 'first' }))
161
- deferredSave(() => api.save({ text: 'second' }))
162
- // Only the 3rd call is executed.
163
- // But all of them are resolved with the result of the 3rd call when `resolveIgnored` is `ResolveIgnored.WITH_LAST`
164
- deferredSave(() => api.save({ text: 'third' })).then(response =>
165
- console.log('Saved!', response),
166
- )
168
+ ```typescript
169
+ const example = async (options = {}) => {
170
+ const df = PromisE.deferred({
171
+ delayMs: 100,
172
+ resolveIgnored: ResolveIgnored.NEVER, // never resolve ignored calls
173
+ ...options,
174
+ })
175
+ df(() => PromisE.delay(5000)).then(console.log)
176
+ df(() => PromisE.delay(500)).then(console.log)
177
+ df(() => PromisE.delay(1000)).then(console.log)
178
+ // delay 2 seconds and invoke df() again
179
+ await PromisE.delay(2000)
180
+ df(() => PromisE.delay(200)).then(console.log)
181
+ }
182
+ example({ ignoreStale: true, throttle: true })
183
+ // `200` will be printed in the console
184
+ example({ ignoreStale: false, throttle: true })
185
+ // `200` and `5000` will be printed in the console
167
186
  ```
168
187
 
169
188
  <div id="deferredCallback"></div>
package/dist/index.d.ts CHANGED
@@ -85,14 +85,18 @@ type DeferredAsyncDefaults = Pick<Required<DeferredAsyncOptions>, 'delayMs' | 'r
85
85
  /** Options for `PromisE.deferred` and other related functions */
86
86
  type DeferredAsyncOptions<ThisArg = unknown, DelayMs extends number = number> = {
87
87
  /**
88
- * Delay in milliseconds, used for `debounce` and `throttle` modes.
88
+ * Delay in milliseconds, used for `debounce` and `throttle` modes. Use `0` for sequential execution.
89
89
  *
90
- * Default: `100`
90
+ * Use `0` to disable debounce/throttle and execute all operations sequentially.
91
+ *
92
+ * Default: `100` (or what is set in `PromisE.deferred.defaults.delayMs`)
91
93
  */
94
+ delayMs?: 0 | PositiveNumber<DelayMs>;
95
+ ignoreStale?: boolean;
92
96
  /** Callback invoked whenever promise/function throws error */
93
97
  onError?: (this: ThisArg, err: unknown) => ValueOrPromise<unknown>;
94
98
  /**
95
- * Whenever a promise/function is ignored when in debource/throttle mode, `onIgnored` wil be invoked.
99
+ * Whenever a promise/function is ignored when in debounce/throttle mode, `onIgnored` wil be invoked.
96
100
  * The promise/function will not be invoked, unless it's manually invoked using the `ignored` function.
97
101
  * Use for debugging or logging purposes.
98
102
  */
@@ -118,17 +122,13 @@ type DeferredAsyncOptions<ThisArg = unknown, DelayMs extends number = number> =
118
122
  thisArg?: ThisArg;
119
123
  } & (({
120
124
  /** Throttle duration in milliseconds */
121
- delayMs?: PositiveNumber<DelayMs>;
125
+ delayMs: PositiveNumber<DelayMs>;
122
126
  throttle: true;
123
- } & Omit<ThrottleOptions, 'onError' | 'ThisArg' | 'tid'>) | ({
127
+ } & Pick<ThrottleOptions, 'trailing'>) | ({
124
128
  /** Debounce/deferred duration in milliseconds */
125
129
  delayMs?: PositiveNumber<DelayMs>;
126
130
  throttle?: false;
127
- } & Omit<DeferredOptions, 'onError' | 'ThisArg' | 'tid'>) | {
128
- /** Sequential execution. All promises will be executed sequentially making sure there is no overlap. */
129
- delayMs: 0;
130
- throttle?: false;
131
- });
131
+ } & Pick<DeferredOptions, 'leading'>));
132
132
  /** Determines what to do when deferred promise/function fails */
133
133
  declare enum ResolveError {
134
134
  /** Neither resolve nor reject the failed */
@@ -176,7 +176,7 @@ type RetryOptions<T = unknown> = {
176
176
  */
177
177
  retryDelay?: number;
178
178
  /**
179
- * Add a random delay between 0ms and `retryDelayJitterMax` to the `retryDelayMs`.
179
+ * Add a random delay between 0ms and `retryDelayJitterMax` to the `retryDelay`.
180
180
  * Default: `true`
181
181
  */
182
182
  retryDelayJitter?: boolean;
@@ -224,7 +224,13 @@ declare class PromisEBase<T = unknown> extends Promise<T> implements IPromisE<T>
224
224
  get rejected(): boolean;
225
225
  /** Indicates if the promise has been resolved */
226
226
  get resolved(): boolean;
227
- /** Get promise status code */
227
+ /**
228
+ * Get promise status code:
229
+ *
230
+ * - `0` = pending
231
+ * - `1` = resolved
232
+ * - `2` = rejected
233
+ */
228
234
  get state(): 0 | 1 | 2;
229
235
  /** Resovle pending promise early. */
230
236
  resolve: (value: T | PromiseLike<T>) => void;
@@ -352,7 +358,7 @@ type TimeoutOptions<Func extends string = 'all'> = {
352
358
  * @example Explanation & example usage:
353
359
  * ```typescript
354
360
  * const example = throttle => {
355
- * const df = PromisE.deferred(throttle)
361
+ * const df = PromisE.deferred({ delayMs: 100, throttle })
356
362
  * df(() => PromisE.delay(5000)).then(console.log)
357
363
  * df(() => PromisE.delay(500)).then(console.log)
358
364
  * df(() => PromisE.delay(1000)).then(console.log)
@@ -378,8 +384,15 @@ type TimeoutOptions<Func extends string = 'all'> = {
378
384
  declare function deferred<T, ThisArg = unknown, Delay extends number = number>(options?: DeferredAsyncOptions<ThisArg, Delay>): DeferredAsyncCallback;
379
385
  declare namespace deferred {
380
386
  var defaults: {
387
+ /**
388
+ * Default delay in milliseconds, used in `debounce` and `throttle` modes.
389
+ *
390
+ * Use `0` (or negative number) to disable debounce/throttle and execute all operations sequentially.
391
+ */
381
392
  delayMs: number;
393
+ /** Set the default error resolution behavior. {@link ResolveError} for all options. */
382
394
  resolveError: ResolveError.REJECT;
395
+ /** Set the default ignored resolution behavior. See {@link ResolveIgnored} for all options. */
383
396
  resolveIgnored: ResolveIgnored.WITH_LAST;
384
397
  };
385
398
  }
@@ -686,7 +699,7 @@ declare class PromisE<T = unknown> extends PromisEBase<T> {
686
699
  * - `'linear'` uses a constant delay.
687
700
  *
688
701
  * Default: `'exponential'`
689
- * @param options.retryDelayMs (optional) The base delay in milliseconds between retries.
702
+ * @param options.retryDelay (optional) The base delay in milliseconds between retries.
690
703
  *
691
704
  * Default: `300`
692
705
  * @param options.retryDelayJitter (optional) If true, adds a random jitter to the delay to prevent
package/dist/index.js CHANGED
@@ -76,7 +76,13 @@ var _PromisEBase = class _PromisEBase extends Promise {
76
76
  get resolved() {
77
77
  return this._state === 1;
78
78
  }
79
- /** Get promise status code */
79
+ /**
80
+ * Get promise status code:
81
+ *
82
+ * - `0` = pending
83
+ * - `1` = resolved
84
+ * - `2` = rejected
85
+ */
80
86
  get state() {
81
87
  return this._state;
82
88
  }
@@ -170,105 +176,124 @@ var ResolveIgnored = /* @__PURE__ */ ((ResolveIgnored2) => {
170
176
 
171
177
  // src/deferred.ts
172
178
  function deferred(options = {}) {
173
- const { defaults } = deferred;
179
+ let sequence = 0;
174
180
  options = objCopy(
175
- defaults,
181
+ deferred.defaults,
176
182
  options,
177
183
  [],
178
184
  "empty"
179
185
  );
180
186
  let { onError, onIgnore, onResult } = options;
181
187
  const {
182
- delayMs,
188
+ delayMs = 0,
183
189
  resolveError,
184
- // by default reject on error
185
190
  resolveIgnored,
186
191
  thisArg,
187
192
  throttle
188
193
  } = options;
189
- let lastPromisE = null;
194
+ let prevQItem = null;
190
195
  const queue = /* @__PURE__ */ new Map();
191
- const gotDelay = isPositiveNumber(delayMs);
196
+ const isSequential = !isPositiveNumber(delayMs);
192
197
  if (thisArg !== void 0) {
193
198
  onError = onError == null ? void 0 : onError.bind(thisArg);
194
199
  onIgnore = onIgnore == null ? void 0 : onIgnore.bind(thisArg);
195
200
  onResult = onResult == null ? void 0 : onResult.bind(thisArg);
196
201
  }
197
- const ignoreOrProceed = (currentId, qItem) => {
198
- lastPromisE = null;
199
- if (!gotDelay) {
200
- queue.delete(currentId);
201
- const [nextId, nextItem] = [...queue.entries()][0] || [];
202
- return nextId && nextItem && execute(nextId, nextItem);
203
- }
204
- const items = [...queue.entries()];
205
- const currentIndex = items.findIndex(([id]) => id === currentId);
206
- for (let i = 0; i <= currentIndex; i++) {
207
- const [iId, iItem] = items[i];
202
+ const handleIgnore = (items, prevQItem2) => {
203
+ for (const [iId, iItem] of items) {
208
204
  queue.delete(iId);
209
- if (iItem == void 0 || iItem.started) continue;
210
- onIgnore && fallbackIfFails2(onIgnore, [iItem.getPromise], void 0);
205
+ if (iItem === void 0 || iItem.started) continue;
206
+ onIgnore && fallbackIfFails2(onIgnore, [iItem.getPromise], 0);
211
207
  switch (resolveIgnored) {
212
208
  case "WITH_UNDEFINED" /* WITH_UNDEFINED */:
213
209
  iItem.resolve(void 0);
214
210
  break;
215
211
  case "WITH_LAST" /* WITH_LAST */:
216
- qItem == null ? void 0 : qItem.then(iItem.resolve, iItem.reject);
212
+ prevQItem2 == null ? void 0 : prevQItem2.then(iItem.resolve, iItem.reject);
217
213
  break;
218
214
  case "NEVER" /* NEVER */:
219
215
  break;
220
216
  }
221
217
  }
218
+ if (!queue.size) sequence = 0;
222
219
  };
223
- const finalizeCb = (resolve, id, qItem) => (resultOrErr) => {
224
- ignoreOrProceed(id, qItem);
225
- if (resolve) {
226
- qItem.resolve(resultOrErr);
227
- onResult && fallbackIfFails2(onResult, [resultOrErr], void 0);
228
- return;
220
+ const handleRemaining = (currentId) => {
221
+ const _prevQItem = prevQItem;
222
+ prevQItem = null;
223
+ if (isSequential) {
224
+ queue.delete(currentId);
225
+ const [nextId, nextItem] = [...queue.entries()][0] || [];
226
+ return nextId && nextItem && handleItem(nextId, nextItem);
229
227
  }
230
- onError && fallbackIfFails2(onError, [resultOrErr], void 0);
231
- switch (resolveError) {
232
- case "REJECT" /* REJECT */:
233
- qItem.reject(resultOrErr);
234
- // eslint-disable-next-line no-fallthrough
235
- case "NEVER" /* NEVER */:
236
- break;
237
- case "RESOLVE_UNDEFINED" /* WITH_UNDEFINED */:
238
- resultOrErr = void 0;
239
- // eslint-disable-next-line no-fallthrough
240
- case "RESOLVE_ERROR" /* WITH_ERROR */:
241
- qItem.resolve(resultOrErr);
242
- break;
228
+ let items = [...queue.entries()];
229
+ if (throttle && options.trailing) {
230
+ items = items.slice(
231
+ 0,
232
+ items.findIndex(([id]) => id === currentId)
233
+ );
234
+ } else if (!throttle && options.leading) {
235
+ items = items.slice(0, -1);
243
236
  }
237
+ handleIgnore(items, _prevQItem);
244
238
  };
245
- const execute = (() => {
246
- const execute2 = (id, qItem) => {
247
- qItem.started = true;
248
- lastPromisE = new PromisEBase_default(qItem.getPromise());
249
- lastPromisE.then(
250
- finalizeCb(true, id, qItem),
251
- finalizeCb(false, id, qItem)
252
- );
253
- };
254
- if (!gotDelay) return execute2;
255
- const deferFn = throttle ? throttledCore : deferredCore;
256
- return deferFn(execute2, delayMs, options);
257
- })();
239
+ let prevSeq = 0;
240
+ const executeItem = async (id, qItem) => {
241
+ var _a;
242
+ qItem.started = true;
243
+ const _prevQItem = prevQItem;
244
+ prevQItem = qItem;
245
+ prevSeq = (_a = prevQItem == null ? void 0 : prevQItem.sequence) != null ? _a : 0;
246
+ try {
247
+ const result = await PromisEBase_default.try(qItem.getPromise);
248
+ const ignore = !isSequential && options.ignoreStale && prevSeq > qItem.sequence;
249
+ if (ignore) return handleIgnore([[id, qItem]], _prevQItem);
250
+ qItem.resolve(result);
251
+ onResult && fallbackIfFails2(onResult, [result], void 0);
252
+ } catch (err) {
253
+ onError && fallbackIfFails2(onError, [err], void 0);
254
+ switch (resolveError) {
255
+ case "REJECT" /* REJECT */:
256
+ qItem.reject(err);
257
+ // eslint-disable-next-line no-fallthrough
258
+ case "NEVER" /* NEVER */:
259
+ break;
260
+ case "RESOLVE_UNDEFINED" /* WITH_UNDEFINED */:
261
+ qItem.resolve(void 0);
262
+ break;
263
+ case "RESOLVE_ERROR" /* WITH_ERROR */:
264
+ qItem.resolve(err);
265
+ break;
266
+ }
267
+ }
268
+ handleRemaining(id);
269
+ };
270
+ const handleItem = isSequential ? executeItem : (throttle ? throttledCore : deferredCore)(
271
+ executeItem,
272
+ delayMs,
273
+ options
274
+ );
258
275
  const deferredFunc = (promise) => {
259
276
  const id = /* @__PURE__ */ Symbol("deferred-queue-item-id");
260
277
  const qItem = new PromisEBase_default();
261
278
  qItem.getPromise = isFn2(promise) ? promise : () => promise;
262
279
  qItem.started = false;
280
+ qItem.sequence = ++sequence;
263
281
  queue.set(id, qItem);
264
- if (gotDelay || !lastPromisE) execute(id, qItem);
282
+ if (!prevQItem || !isSequential) handleItem(id, qItem);
265
283
  return qItem;
266
284
  };
267
285
  return deferredFunc;
268
286
  }
269
287
  deferred.defaults = {
288
+ /**
289
+ * Default delay in milliseconds, used in `debounce` and `throttle` modes.
290
+ *
291
+ * Use `0` (or negative number) to disable debounce/throttle and execute all operations sequentially.
292
+ */
270
293
  delayMs: 100,
294
+ /** Set the default error resolution behavior. {@link ResolveError} for all options. */
271
295
  resolveError: "REJECT" /* REJECT */,
296
+ /** Set the default ignored resolution behavior. See {@link ResolveIgnored} for all options. */
272
297
  resolveIgnored: "WITH_LAST" /* WITH_LAST */
273
298
  };
274
299
  var deferred_default = deferred;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "url": "https://github.com/alien45/superutils/issues"
5
5
  },
6
6
  "dependencies": {
7
- "@superutils/core": "^1.0.8"
7
+ "@superutils/core": "^1.1.1"
8
8
  },
9
9
  "description": "An extended Promise with extra features such as status tracking, deferred/throttled execution, timeout and retry mechanism.",
10
10
  "files": [
@@ -23,7 +23,7 @@
23
23
  "main": "dist/index.js",
24
24
  "name": "@superutils/promise",
25
25
  "peerDpendencies": {
26
- "@superutils/core": "^1.0.8"
26
+ "@superutils/core": "^1.1.1"
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.0.9"
46
+ "version": "1.1.1"
47
47
  }