@superutils/promise 1.0.7 → 1.1.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/dist/index.d.ts CHANGED
@@ -85,14 +85,17 @@ 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>;
92
95
  /** Callback invoked whenever promise/function throws error */
93
96
  onError?: (this: ThisArg, err: unknown) => ValueOrPromise<unknown>;
94
97
  /**
95
- * Whenever a promise/function is ignored when in debource/throttle mode, `onIgnored` wil be invoked.
98
+ * Whenever a promise/function is ignored when in debounce/throttle mode, `onIgnored` wil be invoked.
96
99
  * The promise/function will not be invoked, unless it's manually invoked using the `ignored` function.
97
100
  * Use for debugging or logging purposes.
98
101
  */
@@ -118,13 +121,13 @@ type DeferredAsyncOptions<ThisArg = unknown, DelayMs extends number = number> =
118
121
  thisArg?: ThisArg;
119
122
  } & (({
120
123
  /** Throttle duration in milliseconds */
121
- delayMs?: PositiveNumber<DelayMs>;
124
+ delayMs: PositiveNumber<DelayMs>;
122
125
  throttle: true;
123
- } & Omit<ThrottleOptions, 'onError' | 'ThisArg' | 'tid'>) | ({
126
+ } & Pick<ThrottleOptions, 'trailing'>) | ({
124
127
  /** Debounce/deferred duration in milliseconds */
125
128
  delayMs?: PositiveNumber<DelayMs>;
126
- throttle?: false | undefined;
127
- } & Omit<DeferredOptions, 'onError' | 'ThisArg' | 'tid'>));
129
+ throttle?: false;
130
+ } & Pick<DeferredOptions, 'leading'>));
128
131
  /** Determines what to do when deferred promise/function fails */
129
132
  declare enum ResolveError {
130
133
  /** Neither resolve nor reject the failed */
@@ -143,7 +146,7 @@ declare enum ResolveError {
143
146
  declare enum ResolveIgnored {
144
147
  /** Never resolve ignored promises. Caution: make sure this doesn't cause any memory leaks. */
145
148
  NEVER = "NEVER",
146
- /** (default) resolve with active promise result, the one that caused the current promise/callback to be ignored). */
149
+ /** (default) resolve with active promise result, the one that caused the current promise/callback to be ignored. */
147
150
  WITH_LAST = "WITH_LAST",
148
151
  /** resolve with `undefined` value */
149
152
  WITH_UNDEFINED = "WITH_UNDEFINED"
@@ -172,7 +175,7 @@ type RetryOptions<T = unknown> = {
172
175
  */
173
176
  retryDelay?: number;
174
177
  /**
175
- * Add a random delay between 0ms and `retryDelayJitterMax` to the `retryDelayMs`.
178
+ * Add a random delay between 0ms and `retryDelayJitterMax` to the `retryDelay`.
176
179
  * Default: `true`
177
180
  */
178
181
  retryDelayJitter?: boolean;
@@ -220,7 +223,13 @@ declare class PromisEBase<T = unknown> extends Promise<T> implements IPromisE<T>
220
223
  get rejected(): boolean;
221
224
  /** Indicates if the promise has been resolved */
222
225
  get resolved(): boolean;
223
- /** Get promise status code */
226
+ /**
227
+ * Get promise status code:
228
+ *
229
+ * - `0` = pending
230
+ * - `1` = resolved
231
+ * - `2` = rejected
232
+ */
224
233
  get state(): 0 | 1 | 2;
225
234
  /** Resovle pending promise early. */
226
235
  resolve: (value: T | PromiseLike<T>) => void;
@@ -348,7 +357,7 @@ type TimeoutOptions<Func extends string = 'all'> = {
348
357
  * @example Explanation & example usage:
349
358
  * ```typescript
350
359
  * const example = throttle => {
351
- * const df = PromisE.deferred(throttle)
360
+ * const df = PromisE.deferred({ delayMs: 100, throttle })
352
361
  * df(() => PromisE.delay(5000)).then(console.log)
353
362
  * df(() => PromisE.delay(500)).then(console.log)
354
363
  * df(() => PromisE.delay(1000)).then(console.log)
@@ -374,8 +383,15 @@ type TimeoutOptions<Func extends string = 'all'> = {
374
383
  declare function deferred<T, ThisArg = unknown, Delay extends number = number>(options?: DeferredAsyncOptions<ThisArg, Delay>): DeferredAsyncCallback;
375
384
  declare namespace deferred {
376
385
  var defaults: {
386
+ /**
387
+ * Default delay in milliseconds, used in `debounce` and `throttle` modes.
388
+ *
389
+ * Use `0` (or negative number) to disable debounce/throttle and execute all operations sequentially.
390
+ */
377
391
  delayMs: number;
392
+ /** Set the default error resolution behavior. {@link ResolveError} for all options. */
378
393
  resolveError: ResolveError.REJECT;
394
+ /** Set the default ignored resolution behavior. See {@link ResolveIgnored} for all options. */
379
395
  resolveIgnored: ResolveIgnored.WITH_LAST;
380
396
  };
381
397
  }
@@ -682,7 +698,7 @@ declare class PromisE<T = unknown> extends PromisEBase<T> {
682
698
  * - `'linear'` uses a constant delay.
683
699
  *
684
700
  * Default: `'exponential'`
685
- * @param options.retryDelayMs (optional) The base delay in milliseconds between retries.
701
+ * @param options.retryDelay (optional) The base delay in milliseconds between retries.
686
702
  *
687
703
  * Default: `300`
688
704
  * @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
  }
@@ -169,101 +175,117 @@ var ResolveIgnored = /* @__PURE__ */ ((ResolveIgnored2) => {
169
175
  })(ResolveIgnored || {});
170
176
 
171
177
  // src/deferred.ts
172
- function deferred(options) {
173
- const { defaults } = deferred;
174
- options = objCopy(defaults, options, [], "empty");
178
+ function deferred(options = {}) {
179
+ options = objCopy(
180
+ deferred.defaults,
181
+ options,
182
+ [],
183
+ "empty"
184
+ );
175
185
  let { onError, onIgnore, onResult } = options;
176
186
  const {
177
- delayMs,
187
+ delayMs = 0,
178
188
  resolveError,
179
- // by default reject on error
180
189
  resolveIgnored,
181
190
  thisArg,
182
191
  throttle
183
192
  } = options;
184
- let lastPromisE = null;
193
+ let prevQItem = null;
185
194
  const queue = /* @__PURE__ */ new Map();
186
- const gotDelay = isPositiveNumber(delayMs);
195
+ const isSequential = !isPositiveNumber(delayMs);
187
196
  if (thisArg !== void 0) {
188
197
  onError = onError == null ? void 0 : onError.bind(thisArg);
189
198
  onIgnore = onIgnore == null ? void 0 : onIgnore.bind(thisArg);
190
199
  onResult = onResult == null ? void 0 : onResult.bind(thisArg);
191
200
  }
192
- const ignoreOrProceed = (currentId, qItem) => {
193
- lastPromisE = null;
194
- if (!gotDelay) {
201
+ const handleRemaining = (currentId) => {
202
+ const _prevQItem = prevQItem;
203
+ prevQItem = null;
204
+ if (isSequential) {
195
205
  queue.delete(currentId);
196
206
  const [nextId, nextItem] = [...queue.entries()][0] || [];
197
- return nextId && nextItem && execute(nextId, nextItem);
207
+ return nextId && nextItem && handleItem(nextId, nextItem);
198
208
  }
199
- const items = [...queue.entries()];
200
- const currentIndex = items.findIndex(([id]) => id === currentId);
201
- for (let i = 0; i <= currentIndex; i++) {
202
- const [iId, iItem] = items[i];
203
- queue.delete(iId);
204
- if (iItem == void 0 || iItem.started) continue;
205
- onIgnore && fallbackIfFails2(onIgnore, [iItem.getPromise], void 0);
209
+ let items = [...queue.entries()];
210
+ if (throttle && options.trailing) {
211
+ items = items.slice(
212
+ 0,
213
+ items.findIndex(([id]) => id === currentId)
214
+ );
215
+ } else if (!throttle && options.leading) {
216
+ items = items.slice(0, -1);
217
+ }
218
+ for (const [iId, iItem] of items) {
219
+ !iItem.completed && queue.delete(iId);
220
+ if (iItem === void 0 || iItem.started || iItem.completed)
221
+ continue;
222
+ iItem.completed = true;
223
+ onIgnore && fallbackIfFails2(onIgnore, [iItem.getPromise], 0);
206
224
  switch (resolveIgnored) {
207
225
  case "WITH_UNDEFINED" /* WITH_UNDEFINED */:
208
226
  iItem.resolve(void 0);
209
227
  break;
210
228
  case "WITH_LAST" /* WITH_LAST */:
211
- qItem == null ? void 0 : qItem.then(iItem.resolve, iItem.reject);
229
+ _prevQItem == null ? void 0 : _prevQItem.then(iItem.resolve, iItem.reject);
212
230
  break;
213
231
  case "NEVER" /* NEVER */:
214
232
  break;
215
233
  }
216
234
  }
217
235
  };
218
- const finalizeCb = (resolve, id, qItem) => (resultOrErr) => {
219
- ignoreOrProceed(id, qItem);
220
- if (resolve) {
221
- qItem.resolve(resultOrErr);
222
- onResult && fallbackIfFails2(onResult, [resultOrErr], void 0);
223
- return;
224
- }
225
- onError && fallbackIfFails2(onError, [resultOrErr], void 0);
226
- switch (resolveError) {
227
- case "REJECT" /* REJECT */:
228
- qItem.reject(resultOrErr);
229
- // eslint-disable-next-line no-fallthrough
230
- case "NEVER" /* NEVER */:
231
- break;
232
- case "RESOLVE_UNDEFINED" /* WITH_UNDEFINED */:
233
- resultOrErr = void 0;
234
- // eslint-disable-next-line no-fallthrough
235
- case "RESOLVE_ERROR" /* WITH_ERROR */:
236
- qItem.resolve(resultOrErr);
237
- break;
236
+ const executeItem = async (id, qItem) => {
237
+ if (!id || !(qItem == null ? void 0 : qItem.pending) || qItem.completed) return;
238
+ qItem.started = true;
239
+ prevQItem = qItem;
240
+ try {
241
+ const result = await PromisEBase_default.try(qItem.getPromise);
242
+ qItem.resolve(result);
243
+ onResult && fallbackIfFails2(onResult, [result], void 0);
244
+ } catch (err) {
245
+ onError && fallbackIfFails2(onError, [err], void 0);
246
+ switch (resolveError) {
247
+ case "REJECT" /* REJECT */:
248
+ qItem.reject(err);
249
+ // eslint-disable-next-line no-fallthrough
250
+ case "NEVER" /* NEVER */:
251
+ break;
252
+ case "RESOLVE_UNDEFINED" /* WITH_UNDEFINED */:
253
+ qItem.resolve(void 0);
254
+ break;
255
+ case "RESOLVE_ERROR" /* WITH_ERROR */:
256
+ qItem.resolve(err);
257
+ break;
258
+ }
238
259
  }
260
+ qItem.completed = true;
261
+ handleRemaining(id);
239
262
  };
240
- const execute = (() => {
241
- const execute2 = (id, qItem) => {
242
- qItem.started = true;
243
- lastPromisE = new PromisEBase_default(qItem.getPromise());
244
- lastPromisE.then(
245
- finalizeCb(true, id, qItem),
246
- finalizeCb(false, id, qItem)
247
- );
248
- };
249
- if (!gotDelay) return execute2;
250
- const deferFn = throttle ? throttledCore : deferredCore;
251
- return deferFn(execute2, delayMs, options);
252
- })();
263
+ const handleItem = isSequential ? executeItem : (throttle ? throttledCore : deferredCore)(
264
+ executeItem,
265
+ delayMs,
266
+ options
267
+ );
253
268
  const deferredFunc = (promise) => {
254
- const id = Symbol("deferred-queue-item-id");
269
+ const id = /* @__PURE__ */ Symbol("deferred-queue-item-id");
255
270
  const qItem = new PromisEBase_default();
256
271
  qItem.getPromise = isFn2(promise) ? promise : () => promise;
257
272
  qItem.started = false;
258
273
  queue.set(id, qItem);
259
- if (gotDelay || !lastPromisE) execute(id, qItem);
274
+ if (!prevQItem || !isSequential) handleItem(id, qItem);
260
275
  return qItem;
261
276
  };
262
277
  return deferredFunc;
263
278
  }
264
279
  deferred.defaults = {
280
+ /**
281
+ * Default delay in milliseconds, used in `debounce` and `throttle` modes.
282
+ *
283
+ * Use `0` (or negative number) to disable debounce/throttle and execute all operations sequentially.
284
+ */
265
285
  delayMs: 100,
286
+ /** Set the default error resolution behavior. {@link ResolveError} for all options. */
266
287
  resolveError: "REJECT" /* REJECT */,
288
+ /** Set the default ignored resolution behavior. See {@link ResolveIgnored} for all options. */
267
289
  resolveIgnored: "WITH_LAST" /* WITH_LAST */
268
290
  };
269
291
  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.7"
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.7"
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.7"
46
+ "version": "1.1.0"
47
47
  }