@oscarpalmer/atoms 0.156.0 → 0.158.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/array/index.js +2 -1
- package/dist/array/slice.js +51 -0
- package/dist/atoms.full.js +253 -124
- package/dist/function/index.js +2 -1
- package/dist/function/once.js +97 -0
- package/dist/index.js +3 -1
- package/package.json +3 -3
- package/src/array/index.ts +1 -0
- package/src/array/slice.ts +245 -0
- package/src/function/index.ts +1 -0
- package/src/function/once.ts +188 -0
- package/src/models.ts +35 -0
- package/types/array/index.d.ts +1 -0
- package/types/array/slice.d.ts +82 -0
- package/types/function/index.d.ts +1 -0
- package/types/function/once.d.ts +17 -0
- package/types/models.d.ts +31 -0
package/dist/function/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { noop } from "../internal/function/misc.js";
|
|
2
2
|
import { TIMER_DEBOUNCE, TIMER_THROTTLE, getTimer } from "../internal/function/timer.js";
|
|
3
3
|
import { memoize } from "./memoize.js";
|
|
4
|
+
import { once } from "./once.js";
|
|
4
5
|
/**
|
|
5
6
|
* Debounce a function, ensuring it is only called after `time` milliseconds have passed
|
|
6
7
|
*
|
|
@@ -21,4 +22,4 @@ function debounce(callback, time) {
|
|
|
21
22
|
function throttle(callback, time) {
|
|
22
23
|
return getTimer(TIMER_THROTTLE, callback, time);
|
|
23
24
|
}
|
|
24
|
-
export { debounce, memoize, noop, throttle };
|
|
25
|
+
export { debounce, memoize, noop, once, throttle };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { assert } from "./assert.js";
|
|
2
|
+
/**
|
|
3
|
+
* Create an asynchronous function that can only be called once, rejecting or resolving the same result on subsequent calls
|
|
4
|
+
* @param callback Callback to use once
|
|
5
|
+
* @returns Once callback
|
|
6
|
+
*/
|
|
7
|
+
function asyncOnce(callback) {
|
|
8
|
+
assert(() => typeof callback === "function", MESSAGE_EXPECTATION);
|
|
9
|
+
const state = {
|
|
10
|
+
called: false,
|
|
11
|
+
cleared: false,
|
|
12
|
+
error: false,
|
|
13
|
+
finished: false,
|
|
14
|
+
items: [],
|
|
15
|
+
value: void 0
|
|
16
|
+
};
|
|
17
|
+
const fn = (...parameters) => {
|
|
18
|
+
if (state.cleared) return Promise.reject(new Error(MESSAGE_CLEARED));
|
|
19
|
+
if (state.finished) return state.error ? Promise.reject(state.value) : Promise.resolve(state.value);
|
|
20
|
+
if (state.called) return new Promise((resolve, reject) => {
|
|
21
|
+
state.items.push({
|
|
22
|
+
reject,
|
|
23
|
+
resolve
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
state.called = true;
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
state.items.push({
|
|
29
|
+
reject,
|
|
30
|
+
resolve
|
|
31
|
+
});
|
|
32
|
+
callback(...parameters).then((value) => {
|
|
33
|
+
handleResult(state, value, false);
|
|
34
|
+
}).catch((error) => {
|
|
35
|
+
handleResult(state, error, true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperties(fn, {
|
|
40
|
+
called: { get: () => state.called },
|
|
41
|
+
cleared: { get: () => state.cleared },
|
|
42
|
+
error: { get: () => state.error },
|
|
43
|
+
finished: { get: () => state.finished }
|
|
44
|
+
});
|
|
45
|
+
fn.clear = () => {
|
|
46
|
+
if (!state.called || !state.finished || state.cleared) return;
|
|
47
|
+
state.cleared = true;
|
|
48
|
+
state.value = void 0;
|
|
49
|
+
};
|
|
50
|
+
return fn;
|
|
51
|
+
}
|
|
52
|
+
function handleResult(state, value, error) {
|
|
53
|
+
state.error = error;
|
|
54
|
+
state.finished = true;
|
|
55
|
+
state.value = value;
|
|
56
|
+
const items = state.items.splice(0);
|
|
57
|
+
const { length } = items;
|
|
58
|
+
for (let index = 0; index < length; index += 1) {
|
|
59
|
+
const { reject, resolve } = items[index];
|
|
60
|
+
if (error) reject(value);
|
|
61
|
+
else resolve(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create a function that can only be called once, returning the same value on subsequent calls
|
|
66
|
+
* @param callback Callback to use once
|
|
67
|
+
* @returns Once callback
|
|
68
|
+
*/
|
|
69
|
+
function once(callback) {
|
|
70
|
+
assert(() => typeof callback === "function", MESSAGE_EXPECTATION);
|
|
71
|
+
const state = {
|
|
72
|
+
called: false,
|
|
73
|
+
cleared: false,
|
|
74
|
+
value: void 0
|
|
75
|
+
};
|
|
76
|
+
const fn = (...parameters) => {
|
|
77
|
+
if (state.cleared) throw new Error(MESSAGE_CLEARED);
|
|
78
|
+
if (state.called) return state.value;
|
|
79
|
+
state.called = true;
|
|
80
|
+
state.value = callback(...parameters);
|
|
81
|
+
return state.value;
|
|
82
|
+
};
|
|
83
|
+
Object.defineProperties(fn, {
|
|
84
|
+
called: { get: () => state.called },
|
|
85
|
+
cleared: { get: () => state.cleared }
|
|
86
|
+
});
|
|
87
|
+
fn.clear = () => {
|
|
88
|
+
if (!state.called || state.cleared) return;
|
|
89
|
+
state.cleared = true;
|
|
90
|
+
state.value = void 0;
|
|
91
|
+
};
|
|
92
|
+
return fn;
|
|
93
|
+
}
|
|
94
|
+
once.async = asyncOnce;
|
|
95
|
+
var MESSAGE_CLEARED = "Once has been cleared";
|
|
96
|
+
var MESSAGE_EXPECTATION = "Once expected a function";
|
|
97
|
+
export { once };
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { intersection } from "./array/intersection.js";
|
|
|
17
17
|
import { partition } from "./array/partition.js";
|
|
18
18
|
import { push } from "./array/push.js";
|
|
19
19
|
import { select } from "./array/select.js";
|
|
20
|
+
import { drop, slice, take } from "./array/slice.js";
|
|
20
21
|
import { max } from "./internal/math/aggregate.js";
|
|
21
22
|
import { getString, ignoreKey, join, tryDecode, tryEncode, words } from "./internal/string.js";
|
|
22
23
|
import { compare } from "./internal/value/compare.js";
|
|
@@ -41,6 +42,7 @@ import { hslToHex, hslToRgb, hslToRgba } from "./color/space/hsl.js";
|
|
|
41
42
|
import { getColor } from "./color/index.js";
|
|
42
43
|
import { SizedMap } from "./sized/map.js";
|
|
43
44
|
import { memoize } from "./function/memoize.js";
|
|
45
|
+
import { once } from "./function/once.js";
|
|
44
46
|
import { debounce, throttle } from "./function/index.js";
|
|
45
47
|
import { RetryError, retry } from "./function/retry.js";
|
|
46
48
|
import { isError, isOk, isResult } from "./internal/result.js";
|
|
@@ -75,4 +77,4 @@ import { QueueError, queue } from "./queue.js";
|
|
|
75
77
|
import { getRandomBoolean, getRandomCharacters, getRandomColor, getRandomHex, getRandomItem, getRandomItems } from "./random.js";
|
|
76
78
|
import { attempt } from "./result/index.js";
|
|
77
79
|
import { SizedSet } from "./sized/set.js";
|
|
78
|
-
export { CancelablePromise, PromiseTimeoutError, QueueError, RetryError, SizedMap, SizedSet, attempt, attemptPromise, average, beacon, between, camelCase, cancelable, capitalize, ceil, chunk, clamp, clone, compact, compare, count, debounce, delay, diff, difference, endsWith, equal, error, exists, filter, find, flatten, floor, flow, toResult as fromPromise, fromQuery, toPromise as fromResult, getArray, getColor, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getNormalizedHex, getNumber, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getString, getUuid, getValue, groupBy, hasValue, hexToHsl, hexToHsla, hexToRgb, hexToRgba, hslToHex, hslToRgb, hslToRgba, ignoreKey, includes, indexOf, insert, intersection, isArrayOrPlainObject, isColor, isConstructor, isEmpty, isError, isFulfilled, isHexColor, isHslColor, isHslLike, isHslaColor, isInstanceOf, isKey, isNonNullable, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isRejected, isResult, isRgbColor, isRgbLike, isRgbaColor, isTypedArray, join, kebabCase, logger, lowerCase, max, median, memoize, merge, min, noop, ok, omit, parse, partition, pascalCase, pick, pipe, promises, push, queue, range, retry, rgbToHex, rgbToHsl, rgbToHsla, round, select, setValue, shuffle, smush, snakeCase, sort, splice, startsWith, sum, template, throttle, timed, times, titleCase, toMap, toPromise, toQuery, toRecord, toResult, toSet, toggle, trim, truncate, tryDecode, tryEncode, union, unique, unsmush, unwrap, update, upperCase, words };
|
|
80
|
+
export { CancelablePromise, PromiseTimeoutError, QueueError, RetryError, SizedMap, SizedSet, attempt, attemptPromise, average, beacon, between, camelCase, cancelable, capitalize, ceil, chunk, clamp, clone, compact, compare, count, debounce, delay, diff, difference, drop, endsWith, equal, error, exists, filter, find, flatten, floor, flow, toResult as fromPromise, fromQuery, toPromise as fromResult, getArray, getColor, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getNormalizedHex, getNumber, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getString, getUuid, getValue, groupBy, hasValue, hexToHsl, hexToHsla, hexToRgb, hexToRgba, hslToHex, hslToRgb, hslToRgba, ignoreKey, includes, indexOf, insert, intersection, isArrayOrPlainObject, isColor, isConstructor, isEmpty, isError, isFulfilled, isHexColor, isHslColor, isHslLike, isHslaColor, isInstanceOf, isKey, isNonNullable, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isRejected, isResult, isRgbColor, isRgbLike, isRgbaColor, isTypedArray, join, kebabCase, logger, lowerCase, max, median, memoize, merge, min, noop, ok, omit, once, parse, partition, pascalCase, pick, pipe, promises, push, queue, range, retry, rgbToHex, rgbToHsl, rgbToHsla, round, select, setValue, shuffle, slice, smush, snakeCase, sort, splice, startsWith, sum, take, template, throttle, timed, times, titleCase, toMap, toPromise, toQuery, toRecord, toResult, toSet, toggle, trim, truncate, tryDecode, tryEncode, union, unique, unsmush, unwrap, update, upperCase, words };
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"jsdom": "^28.1",
|
|
11
11
|
"oxfmt": "^0.36",
|
|
12
12
|
"oxlint": "^1.51",
|
|
13
|
-
"rolldown": "1.0.0-rc.
|
|
13
|
+
"rolldown": "1.0.0-rc.7",
|
|
14
14
|
"tslib": "^2.8",
|
|
15
15
|
"typescript": "^5.9",
|
|
16
16
|
"vite": "8.0.0-beta.16",
|
|
@@ -184,5 +184,5 @@
|
|
|
184
184
|
},
|
|
185
185
|
"type": "module",
|
|
186
186
|
"types": "./types/index.d.ts",
|
|
187
|
-
"version": "0.
|
|
188
|
-
}
|
|
187
|
+
"version": "0.158.0"
|
|
188
|
+
}
|
package/src/array/index.ts
CHANGED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import {getArrayCallbacks} from '../internal/array/callbacks';
|
|
2
|
+
import type {PlainObject} from '../models';
|
|
3
|
+
|
|
4
|
+
// #region Types
|
|
5
|
+
|
|
6
|
+
type ExtractType = 'drop' | 'take';
|
|
7
|
+
|
|
8
|
+
// #endregion
|
|
9
|
+
|
|
10
|
+
// #region Functions
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Drop items from the start of an array until they match a value
|
|
14
|
+
* @param array Original array
|
|
15
|
+
* @param key Key to get an item's value for matching
|
|
16
|
+
* @param value Value to match against
|
|
17
|
+
* @returns New array with items dropped
|
|
18
|
+
*/
|
|
19
|
+
export function drop<Item extends PlainObject, Key extends keyof Item>(
|
|
20
|
+
array: Item[],
|
|
21
|
+
key: Key,
|
|
22
|
+
value: Item[Key],
|
|
23
|
+
): Item[];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Drop items from the start of an array until they match a value
|
|
27
|
+
* @param array Original array
|
|
28
|
+
* @param callback Callback to get an item's value for matching
|
|
29
|
+
* @param value Value to match against
|
|
30
|
+
* @return New array with items dropped
|
|
31
|
+
*/
|
|
32
|
+
export function drop<Item, Callback extends (item: Item, index: number, array: Item[]) => unknown>(
|
|
33
|
+
array: Item[],
|
|
34
|
+
callback: Callback,
|
|
35
|
+
value: ReturnType<Callback>,
|
|
36
|
+
): Item[];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Drop items from the start of an array while they match a filter
|
|
40
|
+
* @param array Original array
|
|
41
|
+
* @param callback Filter callback to match items
|
|
42
|
+
* @return New array with items dropped
|
|
43
|
+
*/
|
|
44
|
+
export function drop<Item extends PlainObject>(
|
|
45
|
+
array: Item[],
|
|
46
|
+
callback: (item: Item, index: number, array: Item[]) => boolean,
|
|
47
|
+
): Item[];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Drop a specified number of items, from the start if `>= 0`, or from the end if `< 0`
|
|
51
|
+
* @param array Original array
|
|
52
|
+
* @param count Number of items to drop
|
|
53
|
+
* @returns New array with items dropped
|
|
54
|
+
*/
|
|
55
|
+
export function drop(array: unknown[], count: number): unknown[];
|
|
56
|
+
|
|
57
|
+
export function drop(array: unknown[], first?: unknown, second?: unknown): unknown[] {
|
|
58
|
+
return extract(EXTRACT_DROP, array, first, second);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function extract(
|
|
62
|
+
type: ExtractType,
|
|
63
|
+
array: unknown[],
|
|
64
|
+
first?: unknown,
|
|
65
|
+
second?: unknown,
|
|
66
|
+
): unknown[] {
|
|
67
|
+
if (!Array.isArray(array)) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const {length} = array;
|
|
72
|
+
|
|
73
|
+
if (length === 0) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const isTake = type === EXTRACT_TAKE;
|
|
78
|
+
|
|
79
|
+
if (typeof first === 'number') {
|
|
80
|
+
if (Math.abs(first) >= length) {
|
|
81
|
+
return isTake ? array.slice() : [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (first === 0) {
|
|
85
|
+
return isTake ? [] : array.slice();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (isTake) {
|
|
89
|
+
return first >= 0 ? array.slice(0, first) : array.slice(array.length + first);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return first >= 0 ? array.slice(first) : array.slice(0, array.length + first);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const callbacks =
|
|
96
|
+
second == null ? getArrayCallbacks(first) : getArrayCallbacks(undefined, undefined, first);
|
|
97
|
+
|
|
98
|
+
const isBoolean = callbacks?.bool != null;
|
|
99
|
+
|
|
100
|
+
if (callbacks?.bool == null && callbacks?.value == null) {
|
|
101
|
+
return isTake ? array.slice() : [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const extracted: unknown[] = [];
|
|
105
|
+
|
|
106
|
+
let push = false;
|
|
107
|
+
|
|
108
|
+
for (let index = 0; index < length; index += 1) {
|
|
109
|
+
const item = array[index];
|
|
110
|
+
|
|
111
|
+
const matches = isBoolean
|
|
112
|
+
? callbacks.bool!(item, index, array)
|
|
113
|
+
: Object.is(callbacks!.value!(item, index, array), second);
|
|
114
|
+
|
|
115
|
+
if (isTake) {
|
|
116
|
+
if (isBoolean ? !matches : matches) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
extracted.push(item);
|
|
121
|
+
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (push) {
|
|
126
|
+
extracted.push(item);
|
|
127
|
+
} else if (isBoolean ? !matches : matches) {
|
|
128
|
+
push = true;
|
|
129
|
+
|
|
130
|
+
if (isBoolean) {
|
|
131
|
+
extracted.push(item);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return extracted;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Slice an array, returning a new array with a specified range of items
|
|
141
|
+
* @param array Original array
|
|
142
|
+
* @param start Start index _(inclusive)_
|
|
143
|
+
* @param end End index _(exclusive)_
|
|
144
|
+
* @return New array with sliced items
|
|
145
|
+
*/
|
|
146
|
+
export function slice<Item>(array: Item[], start: number, end: number): Item[];
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Slice an array, returning a new array with a specified number of items
|
|
150
|
+
* @param array Original array
|
|
151
|
+
* @param count Maximum sixe of the new array
|
|
152
|
+
* @return New array with sliced items
|
|
153
|
+
*/
|
|
154
|
+
export function slice<Item>(array: Item[], count: number): Item[];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Slice an array
|
|
158
|
+
* @param array Array to slice
|
|
159
|
+
* @returns Sliced array
|
|
160
|
+
*/
|
|
161
|
+
export function slice<Item>(array: Item[]): Item[];
|
|
162
|
+
|
|
163
|
+
export function slice(array: unknown[], first?: number, second?: number): unknown[] {
|
|
164
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const firstIsNumber = typeof first === 'number';
|
|
169
|
+
const secondIsNumber = typeof second === 'number';
|
|
170
|
+
|
|
171
|
+
if (!firstIsNumber && !secondIsNumber) {
|
|
172
|
+
return array.slice();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!secondIsNumber) {
|
|
176
|
+
return first! >= 0 ? array.slice(0, first) : array.slice(array.length + first!);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!firstIsNumber) {
|
|
180
|
+
return array.slice();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return first! >= 0
|
|
184
|
+
? array.slice(first!, second!)
|
|
185
|
+
: array.slice(array.length + first!, array.length + second!);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Take items from the start of an array until they match a value
|
|
190
|
+
* @param array Original array
|
|
191
|
+
* @param key Key to get an item's value for matching
|
|
192
|
+
* @param value Value to match against
|
|
193
|
+
* @returns New array with taken items
|
|
194
|
+
*/
|
|
195
|
+
export function take<Item extends PlainObject, Key extends keyof Item>(
|
|
196
|
+
array: Item[],
|
|
197
|
+
key: Key,
|
|
198
|
+
value: Item[Key],
|
|
199
|
+
): Item[];
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Take items from the start of an array until they match a value
|
|
203
|
+
* @param array Original array
|
|
204
|
+
* @param callback Callback to get an item's value for matching
|
|
205
|
+
* @param value Value to match against
|
|
206
|
+
* @return New array with taken items
|
|
207
|
+
*/
|
|
208
|
+
export function take<Item, Callback extends (item: Item, index: number, array: Item[]) => unknown>(
|
|
209
|
+
array: Item[],
|
|
210
|
+
callback: Callback,
|
|
211
|
+
value: ReturnType<Callback>,
|
|
212
|
+
): Item[];
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Take items from the start of an array while they match a filter
|
|
216
|
+
* @param array Original array
|
|
217
|
+
* @param callback Filter callback to match items
|
|
218
|
+
* @return New array with taken items
|
|
219
|
+
*/
|
|
220
|
+
export function take<Item extends PlainObject>(
|
|
221
|
+
array: Item[],
|
|
222
|
+
callback: (item: Item, index: number, array: Item[]) => boolean,
|
|
223
|
+
): Item[];
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Take a specified number of items, from the start if `>= 0`, or from the end if `< 0`
|
|
227
|
+
* @param array Original array
|
|
228
|
+
* @param count Number of items to take
|
|
229
|
+
* @returns New array with taken items
|
|
230
|
+
*/
|
|
231
|
+
export function take(array: unknown[], count: number): unknown[];
|
|
232
|
+
|
|
233
|
+
export function take(array: unknown[], first?: unknown, second?: unknown): unknown[] {
|
|
234
|
+
return extract(EXTRACT_TAKE, array, first, second);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// #endregion
|
|
238
|
+
|
|
239
|
+
// #region Variables
|
|
240
|
+
|
|
241
|
+
const EXTRACT_DROP: ExtractType = 'drop';
|
|
242
|
+
|
|
243
|
+
const EXTRACT_TAKE: ExtractType = 'take';
|
|
244
|
+
|
|
245
|
+
// #endregion
|
package/src/function/index.ts
CHANGED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GenericAsyncCallback,
|
|
3
|
+
GenericCallback,
|
|
4
|
+
OnceAsyncCallback,
|
|
5
|
+
OnceCallback,
|
|
6
|
+
} from '../models';
|
|
7
|
+
import {assert} from './assert';
|
|
8
|
+
|
|
9
|
+
// #region Types
|
|
10
|
+
|
|
11
|
+
type OnceAsyncItem<Value> = {
|
|
12
|
+
reject: (reason?: unknown) => void;
|
|
13
|
+
resolve: (value: Value) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type OnceAsyncState<Value> = {
|
|
17
|
+
error: boolean;
|
|
18
|
+
finished: boolean;
|
|
19
|
+
items: OnceAsyncItem<Value>[];
|
|
20
|
+
} & OnceState<Value>;
|
|
21
|
+
|
|
22
|
+
type OnceState<Value> = {
|
|
23
|
+
called: boolean;
|
|
24
|
+
cleared: boolean;
|
|
25
|
+
value: Value;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// #endregion
|
|
29
|
+
|
|
30
|
+
// #region Functions
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create an asynchronous function that can only be called once, rejecting or resolving the same result on subsequent calls
|
|
34
|
+
* @param callback Callback to use once
|
|
35
|
+
* @returns Once callback
|
|
36
|
+
*/
|
|
37
|
+
function asyncOnce<Callback extends GenericAsyncCallback>(
|
|
38
|
+
callback: Callback,
|
|
39
|
+
): OnceAsyncCallback<Callback> {
|
|
40
|
+
assert(() => typeof callback === 'function', MESSAGE_EXPECTATION);
|
|
41
|
+
|
|
42
|
+
const state: OnceAsyncState<Awaited<ReturnType<Callback>>> = {
|
|
43
|
+
called: false,
|
|
44
|
+
cleared: false,
|
|
45
|
+
error: false,
|
|
46
|
+
finished: false,
|
|
47
|
+
items: [],
|
|
48
|
+
value: undefined as never,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const fn = (...parameters: Parameters<Callback>): Promise<Awaited<ReturnType<Callback>>> => {
|
|
52
|
+
if (state.cleared) {
|
|
53
|
+
return Promise.reject(new Error(MESSAGE_CLEARED));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (state.finished) {
|
|
57
|
+
return state.error ? Promise.reject(state.value) : Promise.resolve(state.value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (state.called) {
|
|
61
|
+
return new Promise<Awaited<ReturnType<Callback>>>((resolve, reject) => {
|
|
62
|
+
state.items.push({reject, resolve});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
state.called = true;
|
|
67
|
+
|
|
68
|
+
return new Promise<Awaited<ReturnType<Callback>>>((resolve, reject) => {
|
|
69
|
+
state.items.push({reject, resolve});
|
|
70
|
+
|
|
71
|
+
void callback(...parameters)
|
|
72
|
+
.then(value => {
|
|
73
|
+
handleResult(state, value, false);
|
|
74
|
+
})
|
|
75
|
+
.catch(error => {
|
|
76
|
+
handleResult(state, error, true);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
Object.defineProperties(fn, {
|
|
82
|
+
called: {
|
|
83
|
+
get: (): boolean => state.called,
|
|
84
|
+
},
|
|
85
|
+
cleared: {
|
|
86
|
+
get: (): boolean => state.cleared,
|
|
87
|
+
},
|
|
88
|
+
error: {
|
|
89
|
+
get: (): boolean => state.error,
|
|
90
|
+
},
|
|
91
|
+
finished: {
|
|
92
|
+
get: (): boolean => state.finished,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
fn.clear = (): void => {
|
|
97
|
+
if (!state.called || !state.finished || state.cleared) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
state.cleared = true;
|
|
102
|
+
state.value = undefined as never;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return fn as OnceAsyncCallback<Callback>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function handleResult<Value>(state: OnceAsyncState<Value>, value: unknown, error: boolean): void {
|
|
109
|
+
state.error = error;
|
|
110
|
+
state.finished = true;
|
|
111
|
+
state.value = value as Value;
|
|
112
|
+
|
|
113
|
+
const items = state.items.splice(0);
|
|
114
|
+
const {length} = items;
|
|
115
|
+
|
|
116
|
+
for (let index = 0; index < length; index += 1) {
|
|
117
|
+
const {reject, resolve} = items[index];
|
|
118
|
+
|
|
119
|
+
if (error) {
|
|
120
|
+
reject(value);
|
|
121
|
+
} else {
|
|
122
|
+
resolve(value as Value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create a function that can only be called once, returning the same value on subsequent calls
|
|
129
|
+
* @param callback Callback to use once
|
|
130
|
+
* @returns Once callback
|
|
131
|
+
*/
|
|
132
|
+
export function once<Callback extends GenericCallback>(callback: Callback): OnceCallback<Callback> {
|
|
133
|
+
assert(() => typeof callback === 'function', MESSAGE_EXPECTATION);
|
|
134
|
+
|
|
135
|
+
const state: OnceState<ReturnType<Callback>> = {
|
|
136
|
+
called: false,
|
|
137
|
+
cleared: false,
|
|
138
|
+
value: undefined as never,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const fn = (...parameters: Parameters<Callback>): ReturnType<Callback> => {
|
|
142
|
+
if (state.cleared) {
|
|
143
|
+
throw new Error(MESSAGE_CLEARED);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (state.called) {
|
|
147
|
+
return state.value;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
state.called = true;
|
|
151
|
+
|
|
152
|
+
state.value = callback(...parameters);
|
|
153
|
+
|
|
154
|
+
return state.value;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
Object.defineProperties(fn, {
|
|
158
|
+
called: {
|
|
159
|
+
get: (): boolean => state.called,
|
|
160
|
+
},
|
|
161
|
+
cleared: {
|
|
162
|
+
get: (): boolean => state.cleared,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
fn.clear = (): void => {
|
|
167
|
+
if (!state.called || state.cleared) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
state.cleared = true;
|
|
172
|
+
state.value = undefined as never;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return fn as OnceCallback<Callback>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
once.async = asyncOnce;
|
|
179
|
+
|
|
180
|
+
// #endregion
|
|
181
|
+
|
|
182
|
+
// #region Variables
|
|
183
|
+
|
|
184
|
+
const MESSAGE_CLEARED = 'Once has been cleared';
|
|
185
|
+
|
|
186
|
+
const MESSAGE_EXPECTATION = 'Once expected a function';
|
|
187
|
+
|
|
188
|
+
// #endregion
|
package/src/models.ts
CHANGED
|
@@ -140,6 +140,41 @@ export type NumericalValues<Item extends PlainObject> = {
|
|
|
140
140
|
[Key in keyof Item as Item[Key] extends number ? Key : never]: Item[Key];
|
|
141
141
|
};
|
|
142
142
|
|
|
143
|
+
/**
|
|
144
|
+
* An asynchronous function that can only be called once, returning the same value on subsequent calls
|
|
145
|
+
*/
|
|
146
|
+
export type OnceAsyncCallback<Callback extends GenericAsyncCallback> = {
|
|
147
|
+
/**
|
|
148
|
+
* Did the callback's promise reject?
|
|
149
|
+
*/
|
|
150
|
+
readonly error: boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Has the callback finished?
|
|
153
|
+
*/
|
|
154
|
+
readonly finished: boolean;
|
|
155
|
+
} & Callback &
|
|
156
|
+
OnceCallbackProperties;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* A callback function that can only be called once, returning the same value on subsequent calls
|
|
160
|
+
*/
|
|
161
|
+
export type OnceCallback<Callback extends GenericCallback> = Callback & OnceCallbackProperties;
|
|
162
|
+
|
|
163
|
+
type OnceCallbackProperties = {
|
|
164
|
+
/**
|
|
165
|
+
* Has the callback been called?
|
|
166
|
+
*/
|
|
167
|
+
readonly called: boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Has the callback's value been cleared?
|
|
170
|
+
*/
|
|
171
|
+
readonly cleared: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Clear the callback's cached value
|
|
174
|
+
*/
|
|
175
|
+
clear: () => void;
|
|
176
|
+
};
|
|
177
|
+
|
|
143
178
|
/**
|
|
144
179
|
* A generic object
|
|
145
180
|
*/
|
package/types/array/index.d.ts
CHANGED