@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.
- package/dist/atoms.full.js +185 -72
- package/dist/color/misc/is.js +6 -6
- package/dist/function/memoize.js +1 -1
- package/dist/index.js +7 -6
- package/dist/internal/math/aggregate.js +12 -8
- package/dist/internal/number.js +1 -1
- package/dist/internal/value/partial.js +14 -0
- package/dist/is.js +11 -3
- package/dist/math.js +21 -5
- package/dist/promise.js +107 -36
- package/dist/result.js +7 -5
- package/dist/sized/map.js +1 -1
- package/dist/sized/set.js +2 -2
- package/dist/value/misc.js +3 -2
- package/dist/value/omit.js +11 -0
- package/dist/value/pick.js +11 -0
- package/package.json +5 -5
- package/src/color/misc/is.ts +6 -6
- package/src/function/debounce.ts +2 -2
- package/src/function/memoize.ts +1 -1
- package/src/function/throttle.ts +2 -2
- package/src/index.ts +2 -1
- package/src/internal/function/limiter.ts +3 -3
- package/src/internal/math/aggregate.ts +19 -10
- package/src/internal/number.ts +1 -1
- package/src/internal/value/partial.ts +46 -0
- package/src/is.ts +11 -2
- package/src/math.ts +67 -9
- package/src/models.ts +2 -2
- package/src/promise.ts +249 -57
- package/src/random.ts +2 -2
- package/src/result.ts +31 -11
- package/src/sized/map.ts +1 -1
- package/src/sized/set.ts +2 -2
- package/src/value/get-set.ts +1 -1
- package/src/value/merge.ts +1 -1
- package/src/value/misc.ts +2 -1
- package/src/value/omit.ts +19 -0
- package/src/value/pick.ts +19 -0
- package/types/color/misc/is.d.ts +6 -6
- package/types/function/debounce.d.ts +2 -2
- package/types/function/memoize.d.ts +1 -1
- package/types/function/throttle.d.ts +2 -2
- package/types/index.d.ts +2 -1
- package/types/internal/function/limiter.d.ts +2 -2
- package/types/internal/math/aggregate.d.ts +1 -0
- package/types/internal/number.d.ts +1 -1
- package/types/internal/value/partial.d.ts +2 -0
- package/types/is.d.ts +8 -2
- package/types/math.d.ts +20 -0
- package/types/models.d.ts +2 -2
- package/types/promise.d.ts +68 -11
- package/types/random.d.ts +2 -2
- package/types/result.d.ts +21 -6
- package/types/sized/map.d.ts +1 -1
- package/types/sized/set.d.ts +2 -2
- package/types/value/misc.d.ts +2 -1
- package/types/value/omit.d.ts +8 -0
- package/types/value/{partial.d.ts → pick.d.ts} +1 -1
- package/dist/value/partial.js +0 -17
- 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
|
|
11
|
-
* @returns `true` if the
|
|
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 {
|
|
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
|
-
|
|
82
|
-
return length;
|
|
83
|
-
}
|
|
82
|
+
const callback = getAggregateCallback(key);
|
|
84
83
|
|
|
85
|
-
if (
|
|
86
|
-
return
|
|
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
|
|
16
|
+
* An extend callback that can be canceled
|
|
17
17
|
*/
|
|
18
|
-
export type
|
|
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]:
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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, '
|
|
139
|
-
if (typeof input === '
|
|
140
|
-
return {
|
|
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 {
|
|
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 {
|
|
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 (
|
|
166
|
-
handlers.reject
|
|
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] =
|
|
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
|
|
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['
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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 =>
|
|
367
|
+
.then(value =>
|
|
368
|
+
handleResult(TYPE_FULFILLED, {abort, complete, data, handlers, index, signal, value}),
|
|
369
|
+
)
|
|
277
370
|
.catch(reason =>
|
|
278
|
-
handleResult(TYPE_REJECTED, {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
315
|
-
promise,
|
|
316
|
-
new Promise((_, reject) => {
|
|
317
|
-
rejector = reject;
|
|
501
|
+
signal?.addEventListener(EVENT_NAME, abort, ABORT_OPTIONS);
|
|
318
502
|
|
|
319
|
-
|
|
320
|
-
|
|
503
|
+
const promise = new Promise<Value>((resolve, reject) => {
|
|
504
|
+
rejector = reject;
|
|
321
505
|
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|
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
|
|
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
|
|
76
|
+
* Get a shuffled array
|
|
77
77
|
* @param array Array to get random items from
|
|
78
|
-
* @returns
|
|
78
|
+
* @returns Shuffled version of the original array
|
|
79
79
|
*/
|
|
80
80
|
export function getRandomItems<Value>(array: Value[]): Value[];
|
|
81
81
|
|