@oscarpalmer/atoms 0.149.0 → 0.150.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.
@@ -0,0 +1,33 @@
1
+ import { PROMISE_ABORT_OPTIONS, PROMISE_EVENT_NAME, PROMISE_MESSAGE_EXPECTATION_TIMED, PromiseTimeoutError } from "./models.js";
2
+ import { getPromiseOptions } from "./helpers.js";
3
+ import { settlePromise } from "./misc.js";
4
+ async function getTimedPromise(promise, time, signal) {
5
+ function abort() {
6
+ cancelAnimationFrame(frame);
7
+ rejector(signal.reason);
8
+ }
9
+ function run(now) {
10
+ start ??= now;
11
+ if (time === 0 || now - start >= time - 5) settlePromise(abort, rejector, new PromiseTimeoutError(), signal);
12
+ else frame = requestAnimationFrame(run);
13
+ }
14
+ signal?.addEventListener(PROMISE_EVENT_NAME, abort, PROMISE_ABORT_OPTIONS);
15
+ let frame;
16
+ let rejector;
17
+ let start;
18
+ return Promise.race([promise, new Promise((_, reject) => {
19
+ rejector = reject;
20
+ frame = requestAnimationFrame(run);
21
+ })]).then((value) => {
22
+ cancelAnimationFrame(frame);
23
+ signal?.removeEventListener(PROMISE_EVENT_NAME, abort);
24
+ return value;
25
+ });
26
+ }
27
+ async function timed(promise, options) {
28
+ if (!(promise instanceof Promise)) return Promise.reject(new TypeError(PROMISE_MESSAGE_EXPECTATION_TIMED));
29
+ const { signal, time } = getPromiseOptions(options);
30
+ if (signal?.aborted ?? false) return Promise.reject(signal.reason);
31
+ return time > 0 ? getTimedPromise(promise, time, signal) : promise;
32
+ }
33
+ export { getTimedPromise, timed };
@@ -1,5 +1,7 @@
1
1
  import { isError, isOk, isResult } from "../internal/result.js";
2
- import { attemptPromise } from "../promise.js";
2
+ import { error, getError, ok, toPromise, unwrap } from "./misc.js";
3
+ import { toResult } from "../promise/misc.js";
4
+ import { attemptPromise } from "../promise/index.js";
3
5
  import { matchResult } from "./match.js";
4
6
  import { attemptFlow } from "./work/flow.js";
5
7
  import { attemptPipe } from "./work/pipe.js";
@@ -24,29 +26,4 @@ attempt.flow = attemptFlow;
24
26
  attempt.match = matchResult;
25
27
  attempt.pipe = attemptPipe;
26
28
  attempt.promise = attemptPromise;
27
- function error(value, original) {
28
- return getError(value, original);
29
- }
30
- function getError(value, original) {
31
- const errorResult = {
32
- error: value,
33
- ok: false
34
- };
35
- if (original instanceof Error) errorResult.original = original;
36
- return errorResult;
37
- }
38
- /**
39
- * Creates an ok result
40
- * @param value Value
41
- * @returns Ok result
42
- */
43
- function ok(value) {
44
- return {
45
- ok: true,
46
- value
47
- };
48
- }
49
- function unwrap(value, defaultValue) {
50
- return isOk(value) ? value.value : defaultValue;
51
- }
52
- export { attempt, error, isError, isOk, isResult, ok, unwrap };
29
+ export { attempt, error, toResult as fromPromise, isError, isOk, isResult, ok, toPromise, unwrap };
@@ -0,0 +1,40 @@
1
+ import { isOk, isResult } from "../internal/result.js";
2
+ function error(value, original) {
3
+ return getError(value, original);
4
+ }
5
+ function getError(value, original) {
6
+ const errorResult = {
7
+ error: value,
8
+ ok: false
9
+ };
10
+ if (original instanceof Error) errorResult.original = original;
11
+ return errorResult;
12
+ }
13
+ /**
14
+ * Creates an ok result
15
+ * @param value Value
16
+ * @returns Ok result
17
+ */
18
+ function ok(value) {
19
+ return {
20
+ ok: true,
21
+ value
22
+ };
23
+ }
24
+ /**
25
+ * Converts a result to a promise
26
+ *
27
+ * Resolves if ok, rejects for error
28
+ * @param result Result to convert
29
+ * @returns Promised result
30
+ */
31
+ async function toPromise(result) {
32
+ const actual = typeof result === "function" ? result() : result;
33
+ if (!isResult(actual)) return Promise.reject(new Error(MESSAGE_PROMISE_RESULT));
34
+ return isOk(actual) ? Promise.resolve(actual.value) : Promise.reject(actual.error);
35
+ }
36
+ function unwrap(value, defaultValue) {
37
+ return isOk(value) ? value.value : defaultValue;
38
+ }
39
+ var MESSAGE_PROMISE_RESULT = "toPromise expected to receive a Result";
40
+ export { error, getError, ok, toPromise, unwrap };
package/package.json CHANGED
@@ -94,8 +94,8 @@
94
94
  "default": "./dist/number.js"
95
95
  },
96
96
  "./promise": {
97
- "types": "./types/promise.d.ts",
98
- "default": "./dist/promise.js"
97
+ "types": "./types/promise/index.d.ts",
98
+ "default": "./dist/promise/index.js"
99
99
  },
100
100
  "./query": {
101
101
  "types": "./types/query.d.ts",
@@ -192,5 +192,5 @@
192
192
  },
193
193
  "type": "module",
194
194
  "types": "./types/index.d.ts",
195
- "version": "0.149.0"
195
+ "version": "0.150.0"
196
196
  }
package/src/index.ts CHANGED
@@ -38,7 +38,7 @@ export * from './logger';
38
38
  export * from './math';
39
39
  export * from './models';
40
40
  export * from './number';
41
- export * from './promise';
41
+ export * from './promise/index';
42
42
  export * from './query';
43
43
  export * from './queue';
44
44
  export * from './random';
@@ -0,0 +1,63 @@
1
+ import {getPromiseOptions} from './helpers';
2
+ import {settlePromise} from './misc';
3
+ import {PROMISE_ABORT_OPTIONS, type PromiseOptions} from './models';
4
+
5
+ // #region Functions
6
+
7
+ /**
8
+ * Create a delayed promise that resolves after a certain amount of time, or rejects if aborted
9
+ * @param options Options for the delay
10
+ * @returns Delayed promise
11
+ */
12
+ export function delay(options?: PromiseOptions): Promise<void>;
13
+
14
+ /**
15
+ * Create a delayed promise that resolves after a certain amount of time
16
+ * @param time How long to wait for _(in milliseconds; defaults to `0`)_
17
+ * @returns Delayed promise
18
+ */
19
+ export function delay(time?: number): Promise<void>;
20
+
21
+ export function delay(options?: unknown): Promise<void> {
22
+ const {signal, time} = getPromiseOptions(options);
23
+
24
+ if (signal?.aborted ?? false) {
25
+ return Promise.reject(signal!.reason);
26
+ }
27
+
28
+ function abort(): void {
29
+ cancelAnimationFrame(frame);
30
+
31
+ rejector(signal!.reason);
32
+ }
33
+
34
+ function run(now: DOMHighResTimeStamp): void {
35
+ start ??= now;
36
+
37
+ if (now - start >= time - 5) {
38
+ settlePromise(abort, resolver, undefined, signal);
39
+ } else {
40
+ frame = requestAnimationFrame(run);
41
+ }
42
+ }
43
+
44
+ signal?.addEventListener('abort', abort, PROMISE_ABORT_OPTIONS);
45
+
46
+ let frame: DOMHighResTimeStamp;
47
+ let rejector: (reason: unknown) => void;
48
+ let resolver: () => void;
49
+ let start: DOMHighResTimeStamp;
50
+
51
+ return new Promise((resolve, reject) => {
52
+ rejector = reject;
53
+ resolver = resolve;
54
+
55
+ if (time === 0) {
56
+ settlePromise(abort, resolve, undefined, signal);
57
+ } else {
58
+ frame = requestAnimationFrame(run);
59
+ }
60
+ });
61
+ }
62
+
63
+ // #endregion
@@ -0,0 +1,91 @@
1
+ import type {RequiredKeys} from '../models';
2
+ import {
3
+ PROMISE_STRATEGY_DEFAULT,
4
+ PROMISE_STRATEGY_ALL,
5
+ PROMISE_TYPE_FULFILLED,
6
+ PROMISE_TYPE_REJECTED,
7
+ type FulfilledPromise,
8
+ type PromiseOptions,
9
+ type PromisesOptions,
10
+ type PromisesResultItem,
11
+ type PromiseStrategy,
12
+ type RejectedPromise,
13
+ } from './models';
14
+
15
+ // #region Functions
16
+
17
+ function getNumberOrDefault(value: unknown): number {
18
+ return typeof value === 'number' && value > 0 ? value : 0;
19
+ }
20
+
21
+ export function getPromiseOptions(input: unknown): RequiredKeys<PromiseOptions, 'time'> {
22
+ if (typeof input === 'number') {
23
+ return {
24
+ time: getNumberOrDefault(input),
25
+ };
26
+ }
27
+
28
+ if (input instanceof AbortSignal) {
29
+ return {signal: input, time: 0};
30
+ }
31
+
32
+ const options = typeof input === 'object' && input !== null ? (input as PromiseOptions) : {};
33
+
34
+ return {
35
+ signal: options.signal instanceof AbortSignal ? options.signal : undefined,
36
+ time: getNumberOrDefault(options.time),
37
+ };
38
+ }
39
+
40
+ export function getPromisesOptions(input: unknown): RequiredKeys<PromisesOptions, 'strategy'> {
41
+ if (typeof input === 'string') {
42
+ return {
43
+ strategy: getStrategyOrDefault(input),
44
+ };
45
+ }
46
+
47
+ if (input instanceof AbortSignal) {
48
+ return {signal: input, strategy: PROMISE_STRATEGY_DEFAULT};
49
+ }
50
+
51
+ const options = typeof input === 'object' && input !== null ? (input as PromisesOptions) : {};
52
+
53
+ return {
54
+ signal: options.signal instanceof AbortSignal ? options.signal : undefined,
55
+ strategy: getStrategyOrDefault(options.strategy),
56
+ };
57
+ }
58
+
59
+ export function getStrategyOrDefault(value: unknown): PromiseStrategy {
60
+ return PROMISE_STRATEGY_ALL.has(value as PromiseStrategy)
61
+ ? (value as PromiseStrategy)
62
+ : PROMISE_STRATEGY_DEFAULT;
63
+ }
64
+
65
+ /**
66
+ * Is the value a fulfilled promise result?
67
+ * @param value Value to check
68
+ * @returns `true` if the value is a fulfilled promise result, `false` otherwise
69
+ */
70
+ export function isFulfilled<Value>(value: unknown): value is FulfilledPromise<Value> {
71
+ return isType(value, PROMISE_TYPE_FULFILLED);
72
+ }
73
+
74
+ /**
75
+ * Is the value a rejected promise result?
76
+ * @param value Value to check
77
+ * @returns `true` if the value is a rejected promise result, `false` otherwise
78
+ */
79
+ export function isRejected(value: unknown): value is RejectedPromise {
80
+ return isType(value, PROMISE_TYPE_REJECTED);
81
+ }
82
+
83
+ function isType(value: unknown, type: string): boolean {
84
+ return (
85
+ typeof value === 'object' &&
86
+ value !== null &&
87
+ (value as PromisesResultItem<unknown>).status === type
88
+ );
89
+ }
90
+
91
+ // #endregion
@@ -0,0 +1,230 @@
1
+ import {getPromiseOptions, getPromisesOptions} from './helpers';
2
+ import {handleResult, settlePromise} from './misc';
3
+ import {
4
+ PROMISE_ABORT_OPTIONS,
5
+ PROMISE_EVENT_NAME,
6
+ PROMISE_MESSAGE_EXPECTATION_ATTEMPT,
7
+ PROMISE_MESSAGE_EXPECTATION_PROMISES,
8
+ PROMISE_STRATEGY_DEFAULT,
9
+ PROMISE_TYPE_FULFILLED,
10
+ PROMISE_TYPE_REJECTED,
11
+ type PromiseData,
12
+ type PromiseHandlers,
13
+ type PromiseOptions,
14
+ type Promises,
15
+ type PromisesOptions,
16
+ type PromisesResult,
17
+ } from './models';
18
+ import {getTimedPromise} from './timed';
19
+
20
+ // #region Functions
21
+
22
+ /**
23
+ * Wrap a promise with safety handlers, with optional abort capabilities and timeout
24
+ * @param promise Promise to wrap
25
+ * @param options Options for the promise
26
+ * @returns Wrapped promise
27
+ */
28
+ export async function attemptPromise<Value>(
29
+ promise: Promise<Value>,
30
+ options?: PromiseOptions | AbortSignal | number,
31
+ ): Promise<Value>;
32
+
33
+ /**
34
+ * Wrap a promise-returning callback with safety handlers, with optional abort capabilities and timeout
35
+ * @param callback Callback to wrap
36
+ * @param options Options for the promise
37
+ * @returns Promise-wrapped callback
38
+ */
39
+ export async function attemptPromise<Value>(
40
+ callback: () => Promise<Value>,
41
+ options?: PromiseOptions | AbortSignal | number,
42
+ ): Promise<Value>;
43
+
44
+ /**
45
+ * Wrap a callback with a promise and safety handlers, with optional abort capabilities and timeout
46
+ * @param callback Callback to wrap
47
+ * @param options Options for the promise
48
+ * @returns Promise-wrapped callback
49
+ */
50
+ export async function attemptPromise<Value>(
51
+ callback: () => Value,
52
+ options?: PromiseOptions | AbortSignal | number,
53
+ ): Promise<Value>;
54
+
55
+ export async function attemptPromise<Value>(
56
+ value: (() => Value) | Promise<Value>,
57
+ options?: PromiseOptions | AbortSignal | number,
58
+ ): Promise<Value> {
59
+ const isFunction = typeof value === 'function';
60
+
61
+ if (!isFunction && !(value instanceof Promise)) {
62
+ return Promise.reject(new TypeError(PROMISE_MESSAGE_EXPECTATION_ATTEMPT));
63
+ }
64
+
65
+ const {signal, time} = getPromiseOptions(options);
66
+
67
+ if (signal?.aborted ?? false) {
68
+ return Promise.reject(signal!.reason);
69
+ }
70
+
71
+ function abort(): void {
72
+ rejector(signal!.reason);
73
+ }
74
+
75
+ async function handler(
76
+ resolve: (value: Value) => void,
77
+ reject: (reason: unknown) => void,
78
+ ): Promise<void> {
79
+ try {
80
+ let result = isFunction ? value() : await value;
81
+
82
+ if (result instanceof Promise) {
83
+ result = await result;
84
+ }
85
+
86
+ settlePromise(abort, resolve, result, signal);
87
+ } catch (error) {
88
+ settlePromise(abort, reject, error, signal);
89
+ }
90
+ }
91
+
92
+ let rejector: (reason: unknown) => void;
93
+
94
+ signal?.addEventListener(PROMISE_EVENT_NAME, abort, PROMISE_ABORT_OPTIONS);
95
+
96
+ const promise = new Promise<Value>((resolve, reject) => {
97
+ rejector = reject;
98
+
99
+ handler(resolve, reject);
100
+ });
101
+
102
+ return time > 0 ? getTimedPromise(promise, time, signal) : promise;
103
+ }
104
+
105
+ /**
106
+ * Handle a list of promises, returning their results in an ordered array.
107
+ *
108
+ * Depending on the strategy, the function will either reject on the first error encountered or return an array of rejected and resolved results
109
+ * @param items List of promises
110
+ * @param options Options for handling the promises
111
+ * @returns List of results
112
+ */
113
+ export async function promises<Items extends unknown[], Options extends PromisesOptions>(
114
+ items: Promises<Items>,
115
+ options?: Options,
116
+ ): Promise<Options['strategy'] extends 'first' ? Items : PromisesResult<Items>>;
117
+
118
+ /**
119
+ * Handle a list of promises, returning their results in an ordered array.
120
+ *
121
+ * If any promise in the list is rejected, the whole function will reject
122
+ * @param items List of promises
123
+ * @param strategy Strategy for handling the promises; rejects on the first error encountered
124
+ * @returns List of results
125
+ */
126
+ export async function promises<Items extends unknown[]>(
127
+ items: Promises<Items>,
128
+ strategy: 'first',
129
+ ): Promise<Items>;
130
+
131
+ /**
132
+ * Handle a list of promises, returning their results in an ordered array of rejected and resolved results
133
+ * @param items List of promises
134
+ * @param signal AbortSignal for aborting the operation _(when aborted, the promise will reject with the reason of the signal)_
135
+ * @returns List of results
136
+ */
137
+ export async function promises<Items extends unknown[]>(
138
+ items: Promises<Items>,
139
+ signal?: AbortSignal,
140
+ ): Promise<PromisesResult<Items>>;
141
+
142
+ export async function promises<Items extends unknown[]>(
143
+ items: Promises<Items>,
144
+ options?: unknown,
145
+ ): Promise<Items | PromisesResult<Items>> {
146
+ const {signal, strategy} = getPromisesOptions(options);
147
+
148
+ if (signal?.aborted ?? false) {
149
+ return Promise.reject(signal!.reason);
150
+ }
151
+
152
+ if (!Array.isArray(items)) {
153
+ return Promise.reject(new TypeError(PROMISE_MESSAGE_EXPECTATION_PROMISES));
154
+ }
155
+
156
+ const actual = items.filter(item => item instanceof Promise);
157
+ const {length} = actual;
158
+
159
+ if (length === 0) {
160
+ return actual as unknown as Items | PromisesResult<Items>;
161
+ }
162
+
163
+ const complete = strategy === PROMISE_STRATEGY_DEFAULT;
164
+
165
+ function abort(): void {
166
+ handlers.reject(signal!.reason);
167
+ }
168
+
169
+ signal?.addEventListener('abort', abort, PROMISE_ABORT_OPTIONS);
170
+
171
+ const data: PromiseData<Items> = {
172
+ last: length - 1,
173
+ result: [] as unknown as Items | PromisesResult<Items>,
174
+ };
175
+
176
+ let handlers: PromiseHandlers<Items>;
177
+
178
+ return new Promise((resolve, reject) => {
179
+ handlers = {reject, resolve};
180
+
181
+ for (let index = 0; index < length; index += 1) {
182
+ void actual[index]
183
+ .then(value =>
184
+ handleResult(PROMISE_TYPE_FULFILLED, {
185
+ abort,
186
+ complete,
187
+ data,
188
+ handlers,
189
+ index,
190
+ signal,
191
+ value,
192
+ }),
193
+ )
194
+ .catch(reason =>
195
+ handleResult(PROMISE_TYPE_REJECTED, {
196
+ abort,
197
+ complete,
198
+ data,
199
+ handlers,
200
+ index,
201
+ signal,
202
+ value: reason,
203
+ }),
204
+ );
205
+ }
206
+ });
207
+ }
208
+
209
+ // #endregion
210
+
211
+ // #region Exports
212
+
213
+ export {toPromise as fromResult} from '../result/misc';
214
+ export {delay} from './delay';
215
+ export {isFulfilled, isRejected} from './helpers';
216
+ export {cancelable, toResult} from './misc';
217
+ export {
218
+ CancelablePromise,
219
+ PromiseTimeoutError,
220
+ type FulfilledPromise,
221
+ type RejectedPromise,
222
+ type PromiseOptions,
223
+ type PromiseStrategy,
224
+ type PromisesOptions,
225
+ type PromisesResult,
226
+ type PromisesResultItem,
227
+ } from './models';
228
+ export {timed} from './timed';
229
+
230
+ // #endregion
@@ -0,0 +1,89 @@
1
+ import {error, ok} from '../result/misc';
2
+ import type {Result} from '../result/models';
3
+ import {
4
+ CancelablePromise,
5
+ PROMISE_EVENT_NAME,
6
+ PROMISE_MESSAGE_EXPECTATION_RESULT,
7
+ PROMISE_TYPE_FULFILLED,
8
+ PROMISE_TYPE_REJECTED,
9
+ type PromiseParameters,
10
+ } from './models';
11
+
12
+ // #region Functions
13
+
14
+ /**
15
+ * Create a cancelable promise
16
+ * @param executor Executor function for the promise
17
+ * @returns Cancelable promise
18
+ */
19
+ export function cancelable<Value>(
20
+ executor: (resolve: (value: Value) => void, reject: (reason: unknown) => void) => void,
21
+ ): CancelablePromise<Value> {
22
+ return new CancelablePromise(executor);
23
+ }
24
+
25
+ export function handleResult<Items extends unknown[]>(
26
+ status: string,
27
+ parameters: PromiseParameters<Items>,
28
+ ): void {
29
+ const {abort, complete, data, handlers, index, signal, value} = parameters;
30
+
31
+ if (signal?.aborted ?? false) {
32
+ return;
33
+ }
34
+
35
+ if (!complete && status === PROMISE_TYPE_REJECTED) {
36
+ settlePromise(abort, handlers.reject, value, signal);
37
+
38
+ return;
39
+ }
40
+
41
+ (data.result as unknown[])[index] = !complete
42
+ ? value
43
+ : status === PROMISE_TYPE_FULFILLED
44
+ ? {status, value}
45
+ : {status, reason: value};
46
+
47
+ if (index === data.last) {
48
+ settlePromise(abort, handlers.resolve, data.result, signal);
49
+ }
50
+ }
51
+
52
+ export function settlePromise(
53
+ aborter: () => void,
54
+ settler: (value: any) => void,
55
+ value: unknown,
56
+ signal?: AbortSignal,
57
+ ): void {
58
+ signal?.removeEventListener(PROMISE_EVENT_NAME, aborter);
59
+
60
+ settler(value);
61
+ }
62
+
63
+ /**
64
+ * Converts a promise to a promised result
65
+ * @param callback Promise callback
66
+ * @returns Promised result
67
+ */
68
+ export async function toResult<Value>(callback: () => Promise<Value>): Promise<Result<Value>>;
69
+
70
+ /**
71
+ * Converts a promise to a promised result
72
+ * @param promise Promise to convert
73
+ * @returns Promised result
74
+ */
75
+ export async function toResult<Value>(promise: Promise<Value>): Promise<Result<Value>>;
76
+
77
+ export async function toResult<Value>(
78
+ value: Promise<Value> | (() => Promise<Value>),
79
+ ): Promise<Result<Value>> {
80
+ const actual = typeof value === 'function' ? value() : value;
81
+
82
+ if (!(actual instanceof Promise)) {
83
+ return Promise.reject(new TypeError(PROMISE_MESSAGE_EXPECTATION_RESULT));
84
+ }
85
+
86
+ return actual.then(result => ok(result)).catch(reason => error(reason));
87
+ }
88
+
89
+ // #endregion