@oscarpalmer/atoms 0.141.2 → 0.143.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.
Files changed (61) hide show
  1. package/dist/atoms.full.js +185 -72
  2. package/dist/color/misc/is.js +6 -6
  3. package/dist/function/memoize.js +1 -1
  4. package/dist/index.js +7 -6
  5. package/dist/internal/math/aggregate.js +12 -8
  6. package/dist/internal/number.js +1 -1
  7. package/dist/internal/value/partial.js +14 -0
  8. package/dist/is.js +11 -3
  9. package/dist/math.js +21 -5
  10. package/dist/promise.js +107 -36
  11. package/dist/result.js +7 -5
  12. package/dist/sized/map.js +1 -1
  13. package/dist/sized/set.js +2 -2
  14. package/dist/value/misc.js +3 -2
  15. package/dist/value/omit.js +11 -0
  16. package/dist/value/pick.js +11 -0
  17. package/package.json +5 -5
  18. package/src/color/misc/is.ts +6 -6
  19. package/src/function/debounce.ts +2 -2
  20. package/src/function/memoize.ts +1 -1
  21. package/src/function/throttle.ts +2 -2
  22. package/src/index.ts +2 -1
  23. package/src/internal/function/limiter.ts +3 -3
  24. package/src/internal/math/aggregate.ts +19 -10
  25. package/src/internal/number.ts +1 -1
  26. package/src/internal/value/partial.ts +46 -0
  27. package/src/is.ts +11 -2
  28. package/src/math.ts +67 -9
  29. package/src/models.ts +2 -2
  30. package/src/promise.ts +249 -57
  31. package/src/random.ts +2 -2
  32. package/src/result.ts +31 -11
  33. package/src/sized/map.ts +1 -1
  34. package/src/sized/set.ts +2 -2
  35. package/src/value/get-set.ts +1 -1
  36. package/src/value/merge.ts +1 -1
  37. package/src/value/misc.ts +2 -1
  38. package/src/value/omit.ts +19 -0
  39. package/src/value/pick.ts +19 -0
  40. package/types/color/misc/is.d.ts +6 -6
  41. package/types/function/debounce.d.ts +2 -2
  42. package/types/function/memoize.d.ts +1 -1
  43. package/types/function/throttle.d.ts +2 -2
  44. package/types/index.d.ts +2 -1
  45. package/types/internal/function/limiter.d.ts +2 -2
  46. package/types/internal/math/aggregate.d.ts +1 -0
  47. package/types/internal/number.d.ts +1 -1
  48. package/types/internal/value/partial.d.ts +2 -0
  49. package/types/is.d.ts +8 -2
  50. package/types/math.d.ts +20 -0
  51. package/types/models.d.ts +2 -2
  52. package/types/promise.d.ts +68 -11
  53. package/types/random.d.ts +2 -2
  54. package/types/result.d.ts +21 -6
  55. package/types/sized/map.d.ts +1 -1
  56. package/types/sized/set.d.ts +2 -2
  57. package/types/value/misc.d.ts +2 -1
  58. package/types/value/omit.d.ts +8 -0
  59. package/types/value/{partial.d.ts → pick.d.ts} +1 -1
  60. package/dist/value/partial.js +0 -17
  61. package/src/value/partial.ts +0 -39
@@ -0,0 +1,46 @@
1
+ import type {PlainObject} from '../../models';
2
+
3
+ export function partial<Value extends object, Key extends keyof Value>(
4
+ value: unknown,
5
+ keys: Key[],
6
+ omit: true,
7
+ ): Omit<Value, Key>;
8
+
9
+ export function partial<Value extends object, Key extends keyof Value>(
10
+ value: unknown,
11
+ keys: Key[],
12
+ omit: false,
13
+ ): Pick<Value, Key>;
14
+
15
+ export function partial<Value extends object, Key extends keyof Value>(
16
+ value: unknown,
17
+ providedKeys: Key[],
18
+ omit: boolean,
19
+ ): Partial<Value> {
20
+ if (typeof value !== 'object' || value === null) {
21
+ return {} as Partial<Value>;
22
+ }
23
+
24
+ const keys = omit ? Object.keys(value) : Array.isArray(providedKeys) ? providedKeys : [];
25
+ const {length} = keys;
26
+
27
+ if (length === 0) {
28
+ return omit ? (value as Partial<Value>) : ({} as Partial<Value>);
29
+ }
30
+
31
+ const partials: Partial<Value> = {};
32
+
33
+ for (let index = 0; index < length; index += 1) {
34
+ const key = keys[index];
35
+
36
+ if (!(key in value)) {
37
+ continue;
38
+ }
39
+
40
+ if (omit ? !providedKeys.includes(key as Key) : true) {
41
+ (partials as PlainObject)[key] = (value as PlainObject)[key];
42
+ }
43
+ }
44
+
45
+ return partials;
46
+ }
package/src/is.ts CHANGED
@@ -7,8 +7,8 @@ import type {Primitive} from './models';
7
7
 
8
8
  /**
9
9
  * Is the value empty, or only containing `null` or `undefined` values?
10
- * @param value Object to check
11
- * @returns `true` if the object is considered empty, otherwise `false`
10
+ * @param value Value to check
11
+ * @returns `true` if the value is considered empty, otherwise `false`
12
12
  */
13
13
  export function isEmpty(value: unknown): boolean {
14
14
  if (value == null) {
@@ -31,6 +31,15 @@ export function isEmpty(value: unknown): boolean {
31
31
  return true;
32
32
  }
33
33
 
34
+ /**
35
+ * Is the value not `undefined` or `null`?
36
+ * @param value Value to check
37
+ * @returns `true` if the value is not `undefined` or `null`, otherwise `false`
38
+ */
39
+ export function isNonNullable(value: unknown): value is Exclude<unknown, undefined | null> {
40
+ return value != null;
41
+ }
42
+
34
43
  /**
35
44
  * Is the value `undefined` or `null`?
36
45
  * @param value Value to check
package/src/math.ts CHANGED
@@ -1,4 +1,5 @@
1
- import {aggregate, getAggregated} from './internal/math/aggregate';
1
+ import {isNumber} from './internal/is';
2
+ import {aggregate, getAggregateCallback, getAggregated} from './internal/math/aggregate';
2
3
  import type {NumericalValues, PlainObject} from './models';
3
4
 
4
5
  // #region Functions
@@ -78,17 +79,12 @@ export function count(array: unknown[], key?: unknown, value?: unknown): number
78
79
 
79
80
  const {length} = array;
80
81
 
81
- if (key == null) {
82
- return length;
83
- }
82
+ const callback = getAggregateCallback(key);
84
83
 
85
- if (typeof key !== 'string' && typeof key !== 'function') {
86
- return Number.NaN;
84
+ if (callback == null) {
85
+ return length;
87
86
  }
88
87
 
89
- const callback =
90
- typeof key === 'function' ? key : (item: PlainObject): unknown => item[key as never];
91
-
92
88
  let counted = 0;
93
89
 
94
90
  for (let index = 0; index < length; index += 1) {
@@ -102,6 +98,68 @@ export function count(array: unknown[], key?: unknown, value?: unknown): number
102
98
  return counted;
103
99
  }
104
100
 
101
+ /**
102
+ * Get the median value from a list of items
103
+ * @param array List of items
104
+ * @param callback Callback to get an item's value
105
+ * @returns Median value, or `NaN` if no median can be calculated
106
+ */
107
+ export function median<Item>(
108
+ array: Item[],
109
+ callback: (item: Item, index: number, array: Item[]) => number,
110
+ ): number;
111
+
112
+ /**
113
+ * Get the median value from a list of items
114
+ * @param array List of items
115
+ * @param key Key to use for value
116
+ * @returns Median value, or `NaN` if no median can be calculated
117
+ */
118
+ export function median<Item extends PlainObject>(
119
+ array: Item[],
120
+ key: keyof NumericalValues<Item>,
121
+ ): number;
122
+
123
+ /**
124
+ * Get the median value from a list of numbers
125
+ * @param array List of numbers
126
+ * @returns Median value, or `NaN` if no median can be calculated
127
+ */
128
+ export function median(array: number[]): number;
129
+
130
+ export function median(array: unknown[], key?: unknown): number {
131
+ let length = Array.isArray(array) ? array.length : 0;
132
+
133
+ if (!Array.isArray(array) || length === 0) {
134
+ return Number.NaN;
135
+ }
136
+
137
+ if (length === 1) {
138
+ return isNumber(array[0]) ? array[0] : Number.NaN;
139
+ }
140
+
141
+ let values: unknown[] = array;
142
+
143
+ const callback = getAggregateCallback(key);
144
+
145
+ if (callback != null) {
146
+ values = array.map((item, index) => callback(item as never, index, array));
147
+ }
148
+
149
+ const numbers = values.filter(isNumber).sort((first, second) => first - second);
150
+
151
+ length = numbers.length;
152
+
153
+ if (length % 2 === 0) {
154
+ const first = length / 2 - 1;
155
+ const second = length / 2;
156
+
157
+ return (numbers[first] + numbers[second]) / 2;
158
+ }
159
+
160
+ return numbers[Math.floor(length / 2)];
161
+ }
162
+
105
163
  /**
106
164
  * Get the minimum value from a list of items
107
165
  * @param items List of items
package/src/models.ts CHANGED
@@ -13,9 +13,9 @@ export type ArrayOrPlainObject = unknown[] | Record<PropertyKey, unknown>;
13
13
  export type BuiltIns = void | Date | Primitive | RegExp;
14
14
 
15
15
  /**
16
- * An extend callback that can be cancelled
16
+ * An extend callback that can be canceled
17
17
  */
18
- export type CancellableCallback<Callback extends GenericCallback> = Callback & {
18
+ export type CancelableCallback<Callback extends GenericCallback> = Callback & {
19
19
  /**
20
20
  * Cancel the callback
21
21
  */
package/src/promise.ts CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  import type {RequiredKeys} from './models';
4
4
 
5
+ export class CancelablePromise<Value = void> extends Promise<Value> {
6
+ #rejector!: (reason: unknown) => void;
7
+
8
+ constructor(
9
+ executor: (resolve: (value: Value) => void, reject: (reason: unknown) => void) => void,
10
+ ) {
11
+ let rejector: (reason: unknown) => void;
12
+
13
+ super((resolve, reject) => {
14
+ rejector = reject;
15
+
16
+ executor(resolve, reject);
17
+ });
18
+
19
+ this.#rejector = rejector!;
20
+ }
21
+
22
+ /**
23
+ * Cancel the promise, rejecting it with an optional reason
24
+ * @param reason Optional reason for canceling the promise
25
+ */
26
+ cancel(reason?: unknown): void {
27
+ this.#rejector(reason);
28
+ }
29
+ }
30
+
5
31
  type Data<Items extends unknown[]> = {
6
32
  last: number;
7
33
  result: Items | PromisesResult<Items>;
@@ -18,8 +44,9 @@ type Handlers<Items extends unknown[]> = {
18
44
  };
19
45
 
20
46
  type Parameters<Items extends unknown[]> = {
47
+ abort: () => void;
48
+ complete: boolean;
21
49
  data: Data<Items>;
22
- eager: boolean;
23
50
  handlers: Handlers<Items>;
24
51
  index: number;
25
52
  signal?: AbortSignal;
@@ -37,6 +64,11 @@ type PromiseOptions = {
37
64
  time?: number;
38
65
  };
39
66
 
67
+ /**
68
+ * Promise handling strategy
69
+ */
70
+ export type PromiseStrategy = 'complete' | 'first';
71
+
40
72
  export class PromiseTimeoutError extends Error {
41
73
  constructor() {
42
74
  super(MESSAGE_TIMEOUT);
@@ -50,12 +82,12 @@ type Promises<Items extends unknown[]> = {
50
82
  };
51
83
 
52
84
  type PromisesOptions = {
53
- eager?: boolean;
54
85
  signal?: AbortSignal;
86
+ strategy?: PromiseStrategy;
55
87
  };
56
88
 
57
89
  type PromisesResult<Items extends unknown[]> = {
58
- [K in keyof Items]: PromisesResultItem<Items[K]>;
90
+ [K in keyof Items]: Items[K] extends Promise<infer Value> ? PromisesResultItem<Value> : never;
59
91
  };
60
92
 
61
93
  type PromisesResultItem<Value> = FulfilledPromiseResult<Value> | RejectedPromiseResult;
@@ -69,17 +101,28 @@ type RejectedPromiseResult = {
69
101
 
70
102
  // #region Functions
71
103
 
104
+ /**
105
+ * Create a cancelable promise
106
+ * @param executor Executor function for the promise
107
+ * @returns Cancelable promise
108
+ */
109
+ export function cancelable<Value>(
110
+ executor: (resolve: (value: Value) => void, reject: (reason: unknown) => void) => void,
111
+ ): CancelablePromise<Value> {
112
+ return new CancelablePromise(executor);
113
+ }
114
+
72
115
  /**
73
116
  * Create a delayed promise that resolves after a certain amount of time, or rejects if aborted
74
117
  * @param options Options for the delay
75
- * @returns A delayed promise
118
+ * @returns Delayed promise
76
119
  */
77
120
  export function delay(options?: PromiseOptions): Promise<void>;
78
121
 
79
122
  /**
80
123
  * Create a delayed promise that resolves after a certain amount of time
81
124
  * @param time How long to wait for _(in milliseconds; defaults to `0`)_
82
- * @returns A delayed promise
125
+ * @returns Delayed promise
83
126
  */
84
127
  export function delay(time?: number): Promise<void>;
85
128
 
@@ -96,7 +139,7 @@ export function delay(options?: unknown): Promise<void> {
96
139
  rejector(signal!.reason);
97
140
  }
98
141
 
99
- signal?.addEventListener('abort', abort, abortOptions);
142
+ signal?.addEventListener('abort', abort, ABORT_OPTIONS);
100
143
 
101
144
  let rejector: (reason: unknown) => void;
102
145
  let timeout: ReturnType<typeof setTimeout>;
@@ -105,17 +148,11 @@ export function delay(options?: unknown): Promise<void> {
105
148
  rejector = reject;
106
149
 
107
150
  timeout = setTimeout(() => {
108
- signal?.removeEventListener('abort', abort);
109
-
110
- resolve();
151
+ settlePromise(abort, resolve, undefined, signal);
111
152
  }, time);
112
153
  });
113
154
  }
114
155
 
115
- function getBooleanOrDefault(value: unknown, defaultValue: boolean): boolean {
116
- return typeof value === 'boolean' ? value : defaultValue;
117
- }
118
-
119
156
  function getNumberOrDefault(value: unknown): number {
120
157
  return typeof value === 'number' && value > 0 ? value : 0;
121
158
  }
@@ -127,6 +164,10 @@ function getPromiseOptions(input: unknown): RequiredKeys<PromiseOptions, 'time'>
127
164
  };
128
165
  }
129
166
 
167
+ if (input instanceof AbortSignal) {
168
+ return {signal: input, time: 0};
169
+ }
170
+
130
171
  const options = typeof input === 'object' && input !== null ? (input as PromiseOptions) : {};
131
172
 
132
173
  return {
@@ -135,47 +176,87 @@ function getPromiseOptions(input: unknown): RequiredKeys<PromiseOptions, 'time'>
135
176
  };
136
177
  }
137
178
 
138
- function getPromisesOptions(input: unknown): RequiredKeys<PromisesOptions, 'eager'> {
139
- if (typeof input === 'boolean') {
140
- return {eager: input};
179
+ function getPromisesOptions(input: unknown): RequiredKeys<PromisesOptions, 'strategy'> {
180
+ if (typeof input === 'string') {
181
+ return {
182
+ strategy: getStrategyOrDefault(input),
183
+ };
141
184
  }
142
185
 
143
186
  if (input instanceof AbortSignal) {
144
- return {eager: false, signal: input};
187
+ return {signal: input, strategy: DEFAULT_STRATEGY};
145
188
  }
146
189
 
147
190
  const options = typeof input === 'object' && input !== null ? (input as PromisesOptions) : {};
148
191
 
149
192
  return {
150
- eager: getBooleanOrDefault(options.eager, false),
151
193
  signal: options.signal instanceof AbortSignal ? options.signal : undefined,
194
+ strategy: getStrategyOrDefault(options.strategy),
152
195
  };
153
196
  }
154
197
 
198
+ function getStrategyOrDefault(value: unknown): PromiseStrategy {
199
+ return strategies.has(value as PromiseStrategy) ? (value as PromiseStrategy) : DEFAULT_STRATEGY;
200
+ }
201
+
202
+ async function getTimed<Value>(
203
+ promise: Promise<Value>,
204
+ time: number,
205
+ signal?: AbortSignal,
206
+ ): Promise<Value> {
207
+ function abort(): void {
208
+ clearTimeout(timeout);
209
+
210
+ rejector(signal!.reason);
211
+ }
212
+
213
+ signal?.addEventListener(EVENT_NAME, abort, ABORT_OPTIONS);
214
+
215
+ let rejector: (reason: unknown) => void;
216
+ let timeout: ReturnType<typeof setTimeout>;
217
+
218
+ return Promise.race<Value>([
219
+ promise,
220
+ new Promise((_, reject) => {
221
+ rejector = reject;
222
+
223
+ timeout = setTimeout(() => {
224
+ settlePromise(abort, reject, new PromiseTimeoutError(), signal);
225
+ }, time);
226
+ }),
227
+ ]).then(value => {
228
+ clearTimeout(timeout);
229
+
230
+ signal?.removeEventListener(EVENT_NAME, abort);
231
+
232
+ return value;
233
+ });
234
+ }
235
+
155
236
  function handleResult<Items extends unknown[]>(
156
237
  status: string,
157
238
  parameters: Parameters<Items>,
158
239
  ): void {
159
- const {data, eager, handlers, index, signal, value} = parameters;
240
+ const {abort, complete, data, handlers, index, signal, value} = parameters;
160
241
 
161
242
  if (signal?.aborted ?? false) {
162
243
  return;
163
244
  }
164
245
 
165
- if (eager && status === TYPE_REJECTED) {
166
- handlers.reject(value);
246
+ if (!complete && status === TYPE_REJECTED) {
247
+ settlePromise(abort, handlers.reject, value, signal);
167
248
 
168
249
  return;
169
250
  }
170
251
 
171
- (data.result as unknown[])[index] = eager
252
+ (data.result as unknown[])[index] = !complete
172
253
  ? value
173
254
  : status === TYPE_FULFILLED
174
255
  ? {status, value}
175
256
  : {status, reason: value};
176
257
 
177
258
  if (index === data.last) {
178
- handlers.resolve(data.result as Items | PromisesResult<Items>);
259
+ settlePromise(abort, handlers.resolve, data.result, signal);
179
260
  }
180
261
  }
181
262
 
@@ -206,7 +287,9 @@ function isType(value: unknown, type: string): boolean {
206
287
  }
207
288
 
208
289
  /**
209
- * ---
290
+ * Handle a list of promises, returning their results in an ordered array.
291
+ *
292
+ * Depending on the strategy, the function will either reject on the first error encountered or return an array of rejected and resolved results
210
293
  * @param items List of promises
211
294
  * @param options Options for handling the promises
212
295
  * @return List of results
@@ -214,17 +297,19 @@ function isType(value: unknown, type: string): boolean {
214
297
  export async function promises<Items extends unknown[], Options extends PromisesOptions>(
215
298
  items: Promises<Items>,
216
299
  options?: Options,
217
- ): Promise<Options['eager'] extends true ? Items : PromisesResult<Items>>;
300
+ ): Promise<Options['strategy'] extends 'first' ? Items : PromisesResult<Items>>;
218
301
 
219
302
  /**
220
- * Handle a list of promises, returning their results in an ordered array. If any promise in the list is rejected, the whole function will reject
303
+ * Handle a list of promises, returning their results in an ordered array.
304
+ *
305
+ * If any promise in the list is rejected, the whole function will reject
221
306
  * @param items List of promises
222
- * @param eager Reject immediately if any promise is rejected
307
+ * @param strategy Strategy for handling the promises; rejects on the first error encountered
223
308
  * @return List of results
224
309
  */
225
310
  export async function promises<Items extends unknown[]>(
226
311
  items: Promises<Items>,
227
- eager: true,
312
+ strategy: 'first',
228
313
  ): Promise<Items>;
229
314
 
230
315
  /**
@@ -242,24 +327,30 @@ export async function promises<Items extends unknown[]>(
242
327
  items: Promises<Items>,
243
328
  options?: unknown,
244
329
  ): Promise<Items | PromisesResult<Items>> {
245
- const actual = items.filter(item => item instanceof Promise);
246
- const {length} = actual;
247
-
248
- const {eager, signal} = getPromisesOptions(options);
330
+ const {signal, strategy} = getPromisesOptions(options);
249
331
 
250
332
  if (signal?.aborted ?? false) {
251
333
  return Promise.reject(signal!.reason);
252
334
  }
253
335
 
336
+ if (!Array.isArray(items)) {
337
+ return Promise.reject(new TypeError(MESSAGE_EXPECTATION_PROMISES));
338
+ }
339
+
340
+ const actual = items.filter(item => item instanceof Promise);
341
+ const {length} = actual;
342
+
254
343
  if (length === 0) {
255
344
  return actual as unknown as Items | PromisesResult<Items>;
256
345
  }
257
346
 
347
+ const complete = strategy === DEFAULT_STRATEGY;
348
+
258
349
  function abort(): void {
259
350
  handlers.reject(signal!.reason);
260
351
  }
261
352
 
262
- signal?.addEventListener('abort', abort, abortOptions);
353
+ signal?.addEventListener('abort', abort, ABORT_OPTIONS);
263
354
 
264
355
  const data: Data<Items> = {
265
356
  last: length - 1,
@@ -273,21 +364,57 @@ export async function promises<Items extends unknown[]>(
273
364
 
274
365
  for (let index = 0; index < length; index += 1) {
275
366
  void actual[index]
276
- .then(value => handleResult(TYPE_FULFILLED, {data, eager, handlers, index, signal, value}))
367
+ .then(value =>
368
+ handleResult(TYPE_FULFILLED, {abort, complete, data, handlers, index, signal, value}),
369
+ )
277
370
  .catch(reason =>
278
- handleResult(TYPE_REJECTED, {data, eager, handlers, index, signal, value: reason}),
371
+ handleResult(TYPE_REJECTED, {
372
+ abort,
373
+ complete,
374
+ data,
375
+ handlers,
376
+ index,
377
+ signal,
378
+ value: reason,
379
+ }),
279
380
  );
280
381
  }
281
382
  });
282
383
  }
283
384
 
284
- export function timed(promise: Promise<unknown>, options: PromiseOptions): Promise<unknown>;
385
+ function settlePromise(
386
+ aborter: () => void,
387
+ settler: (value: any) => void,
388
+ value: unknown,
389
+ signal?: AbortSignal,
390
+ ): void {
391
+ signal?.removeEventListener(EVENT_NAME, aborter);
285
392
 
286
- export function timed(promise: Promise<unknown>, time: number): Promise<unknown>;
393
+ settler(value);
394
+ }
395
+
396
+ /**
397
+ * Create a promise that should be settled within a certain amount of time
398
+ * @param promise Promise to settle
399
+ * @param options Timed options
400
+ * @returns Timed promise
401
+ */
402
+ export async function timed<Value>(
403
+ promise: Promise<Value>,
404
+ options: RequiredKeys<PromiseOptions, 'time'>,
405
+ ): Promise<Value>;
287
406
 
288
- export function timed(promise: Promise<unknown>, options: unknown): Promise<unknown> {
407
+ /**
408
+ * Create a promise that should be settled within a certain amount of time
409
+ * @param promise Promise to settle
410
+ * @param time How long to wait for _(in milliseconds; defaults to `0`)_
411
+ * @returns Timed promise
412
+ */
413
+ export async function timed<Value>(promise: Promise<Value>, time: number): Promise<Value>;
414
+
415
+ export async function timed<Value>(promise: Promise<Value>, options: unknown): Promise<Value> {
289
416
  if (!(promise instanceof Promise)) {
290
- throw new TypeError(MESSAGE_EXPECTATION);
417
+ return Promise.reject(new TypeError(MESSAGE_EXPECTATION_TIMED));
291
418
  }
292
419
 
293
420
  const {signal, time} = getPromiseOptions(options);
@@ -296,49 +423,114 @@ export function timed(promise: Promise<unknown>, options: unknown): Promise<unkn
296
423
  return Promise.reject(signal!.reason);
297
424
  }
298
425
 
299
- if (time <= 0) {
300
- return promise;
426
+ return time > 0 ? getTimed(promise, time, signal) : promise;
427
+ }
428
+
429
+ /**
430
+ * Wrap a promise with safety handlers, with optional abort capabilities and timeout
431
+ * @param promise Promise to wrap
432
+ * @param options Options for the promise
433
+ * @returns Wrapped promise
434
+ */
435
+ export async function attemptPromise<Value>(
436
+ promise: Promise<Value>,
437
+ options?: PromiseOptions | AbortSignal | number,
438
+ ): Promise<Value>;
439
+
440
+ /**
441
+ * Wrap a promise-returning callback with safety handlers, with optional abort capabilities and timeout
442
+ * @param callback Callback to wrap
443
+ * @param options Options for the promise
444
+ * @returns Promise-wrapped callback
445
+ */
446
+ export async function attemptPromise<Value>(
447
+ callback: () => Promise<Value>,
448
+ options?: PromiseOptions | AbortSignal | number,
449
+ ): Promise<Value>;
450
+
451
+ /**
452
+ * Wrap a callback with a promise and safety handlers, with optional abort capabilities and timeout
453
+ * @param callback Callback to wrap
454
+ * @param options Options for the promise
455
+ * @returns Promise-wrapped callback
456
+ */
457
+ export async function attemptPromise<Value>(
458
+ callback: () => Value,
459
+ options?: PromiseOptions | AbortSignal | number,
460
+ ): Promise<Value>;
461
+
462
+ export async function attemptPromise<Value>(
463
+ value: (() => Value) | Promise<Value>,
464
+ options?: PromiseOptions | AbortSignal | number,
465
+ ): Promise<Value> {
466
+ const isFunction = typeof value === 'function';
467
+
468
+ if (!isFunction && !(value instanceof Promise)) {
469
+ return Promise.reject(new TypeError(MESSAGE_EXPECTATION_ATTEMPT));
470
+ }
471
+
472
+ const {signal, time} = getPromiseOptions(options);
473
+
474
+ if (signal?.aborted ?? false) {
475
+ return Promise.reject(signal!.reason);
301
476
  }
302
477
 
303
478
  function abort(): void {
304
- clearTimeout(timeout);
305
-
306
479
  rejector(signal!.reason);
307
480
  }
308
481
 
309
- signal?.addEventListener(EVENT_NAME, abort, abortOptions);
482
+ async function handler(
483
+ resolve: (value: Value) => void,
484
+ reject: (reason: unknown) => void,
485
+ ): Promise<void> {
486
+ try {
487
+ let result = isFunction ? value() : await value;
488
+
489
+ if (result instanceof Promise) {
490
+ result = await result;
491
+ }
492
+
493
+ settlePromise(abort, resolve, result, signal);
494
+ } catch (error) {
495
+ settlePromise(abort, reject, error, signal);
496
+ }
497
+ }
310
498
 
311
499
  let rejector: (reason: unknown) => void;
312
- let timeout: ReturnType<typeof setTimeout>;
313
500
 
314
- return Promise.race([
315
- promise,
316
- new Promise((_, reject) => {
317
- rejector = reject;
501
+ signal?.addEventListener(EVENT_NAME, abort, ABORT_OPTIONS);
318
502
 
319
- timeout = setTimeout(() => {
320
- signal?.removeEventListener(EVENT_NAME, abort);
503
+ const promise = new Promise<Value>((resolve, reject) => {
504
+ rejector = reject;
321
505
 
322
- reject(new PromiseTimeoutError());
323
- }, time);
324
- }),
325
- ]);
506
+ handler(resolve, reject);
507
+ });
508
+
509
+ return time > 0 ? getTimed(promise, time, signal) : promise;
326
510
  }
327
511
 
328
512
  // #endregion
329
513
 
330
514
  // #region Variables
331
515
 
332
- const abortOptions = {once: true};
516
+ const ABORT_OPTIONS = {once: true};
517
+
518
+ const DEFAULT_STRATEGY: PromiseStrategy = 'complete';
333
519
 
334
520
  const ERROR_NAME = 'PromiseTimeoutError';
335
521
 
336
522
  const EVENT_NAME = 'abort';
337
523
 
338
- const MESSAGE_EXPECTATION = 'Timed function expected a Promise';
524
+ const MESSAGE_EXPECTATION_ATTEMPT = 'Attempt expected a function or a promise';
525
+
526
+ const MESSAGE_EXPECTATION_PROMISES = 'Promises expected an array of promises';
527
+
528
+ const MESSAGE_EXPECTATION_TIMED = 'Timed function expected a Promise';
339
529
 
340
530
  const MESSAGE_TIMEOUT = 'Promise timed out';
341
531
 
532
+ const strategies = new Set<PromiseStrategy>(['complete', 'first']);
533
+
342
534
  const TYPE_FULFILLED = 'fulfilled';
343
535
 
344
536
  const TYPE_REJECTED = 'rejected';
package/src/random.ts CHANGED
@@ -73,9 +73,9 @@ export function getRandomItem<Value>(array: Value[]): Value | undefined {
73
73
  export function getRandomItems<Value>(array: Value[], amount: number): Value[];
74
74
 
75
75
  /**
76
- * Get an shuffled array
76
+ * Get a shuffled array
77
77
  * @param array Array to get random items from
78
- * @returns A shuffled version of the original array
78
+ * @returns Shuffled version of the original array
79
79
  */
80
80
  export function getRandomItems<Value>(array: Value[]): Value[];
81
81