@planet-matrix/mobius-model 0.3.0 → 0.4.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/CHANGELOG.md +7 -0
- package/README.md +4 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +18 -3
- package/package.json +3 -3
- package/scripts/build.ts +4 -4
- package/src/basic/README.md +143 -0
- package/src/basic/array.ts +872 -0
- package/src/basic/bigint.ts +114 -0
- package/src/basic/boolean.ts +180 -0
- package/src/basic/error.ts +51 -0
- package/src/basic/function.ts +453 -0
- package/src/basic/helper.ts +276 -0
- package/src/basic/index.ts +15 -0
- package/src/basic/is.ts +320 -0
- package/src/basic/number.ts +178 -0
- package/src/basic/object.ts +58 -0
- package/src/basic/promise.ts +464 -0
- package/src/basic/regexp.ts +7 -0
- package/src/basic/stream.ts +140 -0
- package/src/basic/string.ts +308 -0
- package/src/basic/symbol.ts +164 -0
- package/src/basic/temporal.ts +224 -0
- package/src/index.ts +2 -0
- package/src/type/README.md +330 -0
- package/src/type/array.ts +5 -0
- package/src/type/boolean.ts +471 -0
- package/src/type/class.ts +419 -0
- package/src/type/function.ts +1519 -0
- package/src/type/helper.ts +135 -0
- package/src/type/index.ts +14 -0
- package/src/type/intersection.ts +93 -0
- package/src/type/is.ts +247 -0
- package/src/type/iteration.ts +233 -0
- package/src/type/number.ts +732 -0
- package/src/type/object.ts +788 -0
- package/src/type/path.ts +73 -0
- package/src/type/string.ts +1004 -0
- package/src/type/tuple.ts +2424 -0
- package/src/type/union.ts +108 -0
- package/tests/unit/basic/array.spec.ts +290 -0
- package/tests/unit/basic/bigint.spec.ts +50 -0
- package/tests/unit/basic/boolean.spec.ts +74 -0
- package/tests/unit/basic/error.spec.ts +32 -0
- package/tests/unit/basic/function.spec.ts +175 -0
- package/tests/unit/basic/helper.spec.ts +118 -0
- package/tests/unit/basic/number.spec.ts +74 -0
- package/tests/unit/basic/object.spec.ts +15 -0
- package/tests/unit/basic/promise.spec.ts +232 -0
- package/tests/unit/basic/regexp.spec.ts +11 -0
- package/tests/unit/basic/stream.spec.ts +120 -0
- package/tests/unit/basic/string.spec.ts +74 -0
- package/tests/unit/basic/symbol.spec.ts +72 -0
- package/tests/unit/basic/temporal.spec.ts +78 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/reactor/index.d.ts +0 -3
- package/dist/reactor/index.d.ts.map +0 -1
- package/dist/reactor/reactor-core/flags.d.ts +0 -99
- package/dist/reactor/reactor-core/flags.d.ts.map +0 -1
- package/dist/reactor/reactor-core/index.d.ts +0 -4
- package/dist/reactor/reactor-core/index.d.ts.map +0 -1
- package/dist/reactor/reactor-core/primitive.d.ts +0 -276
- package/dist/reactor/reactor-core/primitive.d.ts.map +0 -1
- package/dist/reactor/reactor-core/reactive-system.d.ts +0 -241
- package/dist/reactor/reactor-core/reactive-system.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/branch.d.ts +0 -19
- package/dist/reactor/reactor-operators/branch.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/convert.d.ts +0 -30
- package/dist/reactor/reactor-operators/convert.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/create.d.ts +0 -26
- package/dist/reactor/reactor-operators/create.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/filter.d.ts +0 -269
- package/dist/reactor/reactor-operators/filter.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/index.d.ts +0 -8
- package/dist/reactor/reactor-operators/index.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/join.d.ts +0 -48
- package/dist/reactor/reactor-operators/join.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/map.d.ts +0 -165
- package/dist/reactor/reactor-operators/map.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/utility.d.ts +0 -48
- package/dist/reactor/reactor-operators/utility.d.ts.map +0 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a value to a 0-1 range based on min and max.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```
|
|
6
|
+
* // Expect: 0.5
|
|
7
|
+
* const example1 = normalize(5, 0, 10)
|
|
8
|
+
* // Expect: 0
|
|
9
|
+
* const example2 = normalize(2, 2, 6)
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export const normalize = (value: number, min: number, max: number): number => {
|
|
13
|
+
if (min === max) {
|
|
14
|
+
throw new Error("Min and max value are the same.")
|
|
15
|
+
}
|
|
16
|
+
if (value < min || value > max) {
|
|
17
|
+
throw new Error(`Value ${value} is not in the range of ${min} to ${max}.`)
|
|
18
|
+
}
|
|
19
|
+
return (value - min) / (max - min)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if a number is even.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```
|
|
27
|
+
* // Expect: true
|
|
28
|
+
* const example1 = isEven(10)
|
|
29
|
+
* // Expect: false
|
|
30
|
+
* const example2 = isEven(7)
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const isEven = (x: number): boolean => x % 2 === 0
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if a number is odd.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```
|
|
40
|
+
* // Expect: true
|
|
41
|
+
* const example1 = isOdd(7)
|
|
42
|
+
* // Expect: false
|
|
43
|
+
* const example2 = isOdd(10)
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export const isOdd = (x: number): boolean => x % 2 !== 0
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Clamp a number to a maximum value.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```
|
|
53
|
+
* // Expect: 5
|
|
54
|
+
* const example1 = maxTo(5, 10)
|
|
55
|
+
* // Expect: 3
|
|
56
|
+
* const example2 = maxTo(5, 3)
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export const maxTo = (max: number, x: number): number => x > max ? max : x
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Clamp a number to a minimum value.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```
|
|
66
|
+
* // Expect: 5
|
|
67
|
+
* const example1 = minTo(5, 3)
|
|
68
|
+
* // Expect: 8
|
|
69
|
+
* const example2 = minTo(5, 8)
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export const minTo = (min: number, x: number): number => x < min ? min : x
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the smaller of two numbers.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```
|
|
79
|
+
* // Expect: 2
|
|
80
|
+
* const example1 = minOf(2, 9)
|
|
81
|
+
* // Expect: -1
|
|
82
|
+
* const example2 = minOf(5, -1)
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export const minOf = (x: number, y: number): number => x < y ? x : y
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get the larger of two numbers.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```
|
|
92
|
+
* // Expect: 9
|
|
93
|
+
* const example1 = maxOf(2, 9)
|
|
94
|
+
* // Expect: 5
|
|
95
|
+
* const example2 = maxOf(5, -1)
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export const maxOf = (x: number, y: number): number => x > y ? x : y
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Clamp a number between two bounds.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```
|
|
105
|
+
* // Expect: 5
|
|
106
|
+
* const example1 = between(0, 10, 5)
|
|
107
|
+
* // Expect: 0
|
|
108
|
+
* const example2 = between(0, 10, -3)
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export const between = (a: number, b: number, x: number): number => {
|
|
112
|
+
const min = minOf(a, b)
|
|
113
|
+
const max = maxOf(a, b)
|
|
114
|
+
return x < min ? min : (x > max ? max : x)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get a random integer between min and max, inclusive.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```
|
|
122
|
+
* // Expect: 4
|
|
123
|
+
* const example1 = randomBetween(4, 4)
|
|
124
|
+
* // Expect: 10
|
|
125
|
+
* const example2 = randomBetween(10, 10)
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export const randomBetween = (min: number, max: number): number => {
|
|
129
|
+
return Math.floor(Math.random() * (max - min + 1) + min)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get a random integer between two values, regardless of order, inclusive.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```
|
|
137
|
+
* // Expect: 4
|
|
138
|
+
* const example1 = randomIntBetween(4, 4)
|
|
139
|
+
* // Expect: 10
|
|
140
|
+
* const example2 = randomIntBetween(10, 10)
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export const randomIntBetween = (a: number, b: number): number => {
|
|
144
|
+
const min = minOf(a, b)
|
|
145
|
+
const max = maxOf(a, b)
|
|
146
|
+
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface NumberConstraints {
|
|
150
|
+
min?: number | undefined
|
|
151
|
+
max?: number | undefined
|
|
152
|
+
step?: number | undefined
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Constrain a number by step size and min/max bounds.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```
|
|
160
|
+
* // Expect: 10
|
|
161
|
+
* const example1 = constrainNumber(12, { step: 5, max: 10 })
|
|
162
|
+
* // Expect: 3
|
|
163
|
+
* const example2 = constrainNumber(2.6, { step: 0.5, min: 3 })
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export const constrainNumber = (value: number, constraints: NumberConstraints): number => {
|
|
167
|
+
let constrainedValue = value
|
|
168
|
+
if (constraints.step !== undefined) {
|
|
169
|
+
constrainedValue = Math.round(constrainedValue / constraints.step) * constraints.step
|
|
170
|
+
}
|
|
171
|
+
if (constraints.min !== undefined) {
|
|
172
|
+
constrainedValue = Math.max(constraints.min, constrainedValue)
|
|
173
|
+
}
|
|
174
|
+
if (constraints.max !== undefined) {
|
|
175
|
+
constrainedValue = Math.min(constraints.max, constrainedValue)
|
|
176
|
+
}
|
|
177
|
+
return constrainedValue
|
|
178
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { AnyRecord } from "../type/index.ts"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return a new object that includes only the specified keys from the source object.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```
|
|
8
|
+
* // Expect: { a: 1, c: 3 }
|
|
9
|
+
* const example1 = includeFields({ a: 1, b: 2, c: 3 }, ["a", "c"])
|
|
10
|
+
* // Expect: {}
|
|
11
|
+
* const example2 = includeFields(null, ["a"])
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export const includeFields = <T extends AnyRecord, K extends keyof T>(
|
|
15
|
+
object: T | null | undefined,
|
|
16
|
+
keys: K[],
|
|
17
|
+
): Pick<T, K> => {
|
|
18
|
+
if (object === null || object === undefined) {
|
|
19
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
20
|
+
return {} as Pick<T, K>
|
|
21
|
+
}
|
|
22
|
+
const newObject: Partial<Pick<T, K>> = {} // 初始化一个部分类型的对象
|
|
23
|
+
keys.forEach((key) => {
|
|
24
|
+
if (key in object) {
|
|
25
|
+
newObject[key] = object[key]
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
29
|
+
return newObject as Pick<T, K> // 使用正确的变量名并断言返回类型
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Return a new object that excludes the specified keys from the source object.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```
|
|
37
|
+
* // Expect: { a: 1 }
|
|
38
|
+
* const example1 = excludeFields({ a: 1, b: 2 }, ["b"])
|
|
39
|
+
* // Expect: {}
|
|
40
|
+
* const example2 = excludeFields(undefined, ["a"])
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export const excludeFields = <T extends AnyRecord, K extends keyof T>(
|
|
44
|
+
object: T | null | undefined,
|
|
45
|
+
keys: K[],
|
|
46
|
+
): Omit<T, K> => {
|
|
47
|
+
if (object === null || object === undefined) {
|
|
48
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
49
|
+
return {} as Omit<T, K>
|
|
50
|
+
}
|
|
51
|
+
const newObject: Partial<T> = { ...object } // 创建原对象的浅拷贝
|
|
52
|
+
keys.forEach((key) => {
|
|
53
|
+
// oxlint-disable-next-line no-dynamic-delete
|
|
54
|
+
delete newObject[key] // 删除指定的键
|
|
55
|
+
})
|
|
56
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
57
|
+
return newObject as Omit<T, K> // 使用Omit类型确保返回的类型反映了被排除的键
|
|
58
|
+
}
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { isPlainObject } from "./is.ts"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chain a promise with a fulfillment callback and return the transformed result.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```
|
|
8
|
+
* // Expect: 6
|
|
9
|
+
* const example1 = await promiseThen((value: number) => value * 2, Promise.resolve(3))
|
|
10
|
+
* // Expect: "ok!"
|
|
11
|
+
* const example2 = await promiseThen((value: string) => `${value}!`, Promise.resolve("ok"))
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export const promiseThen = async <V, R>(
|
|
15
|
+
doSomething: (value: V) => R | PromiseLike<R>,
|
|
16
|
+
target: Promise<V>,
|
|
17
|
+
): Promise<R> => {
|
|
18
|
+
return await target.then(doSomething)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Handle a rejected promise and convert it to a resolved fallback value.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```
|
|
26
|
+
* // Expect: "fallback"
|
|
27
|
+
* const example1 = await promiseCatch(() => "fallback", Promise.reject(new Error("x")))
|
|
28
|
+
* // Expect: 3
|
|
29
|
+
* const example2 = await promiseCatch(() => 0, Promise.resolve(3))
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const promiseCatch = async <V, R>(
|
|
33
|
+
doSomething: (reason: unknown) => R | PromiseLike<R>,
|
|
34
|
+
target: Promise<V>,
|
|
35
|
+
): Promise<V | R> => {
|
|
36
|
+
return await target.catch(doSomething)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Run a callback after a promise settles and keep the original resolved value.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```
|
|
44
|
+
* let cleaned = false
|
|
45
|
+
* // Expect: 10
|
|
46
|
+
* const example1 = await promiseFinally(() => { cleaned = true }, Promise.resolve(10))
|
|
47
|
+
* // Expect: true
|
|
48
|
+
* const example2 = cleaned
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export const promiseFinally = async <V>(
|
|
52
|
+
doSomething: () => void,
|
|
53
|
+
target: Promise<V>,
|
|
54
|
+
): Promise<V> => {
|
|
55
|
+
return await target.finally(doSomething)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const INTERNAL_PROMISE_FAIL_RESULT_TYPE: symbol = Symbol("fail")
|
|
59
|
+
export interface PromiseFailResult {
|
|
60
|
+
__type__: typeof INTERNAL_PROMISE_FAIL_RESULT_TYPE
|
|
61
|
+
reason: unknown
|
|
62
|
+
}
|
|
63
|
+
export interface PromiseIndexedFailResult extends PromiseFailResult {
|
|
64
|
+
index: number
|
|
65
|
+
}
|
|
66
|
+
export const promiseConstructFailResult = (reason: unknown): PromiseFailResult => {
|
|
67
|
+
return { __type__: INTERNAL_PROMISE_FAIL_RESULT_TYPE, reason }
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Predicate whether the target is a promise fail result.
|
|
71
|
+
*/
|
|
72
|
+
export const isPromiseFailResult = (target: unknown): target is PromiseFailResult => {
|
|
73
|
+
return isPlainObject(target) && target["__type__"] === INTERNAL_PROMISE_FAIL_RESULT_TYPE
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Designed to use as `onrejected` callback of `Promise.catch`
|
|
77
|
+
* to return a standard `PromiseFailResult`.
|
|
78
|
+
*/
|
|
79
|
+
export const promiseCatchToFailResult = (reason: unknown): PromiseFailResult => {
|
|
80
|
+
return { __type__: INTERNAL_PROMISE_FAIL_RESULT_TYPE, reason }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Giving an array, filter non-`PromiseFailResult` items.
|
|
85
|
+
*/
|
|
86
|
+
export const promiseFilterSuccessResults = <V>(
|
|
87
|
+
results: Array<V | PromiseFailResult>,
|
|
88
|
+
): V[] => {
|
|
89
|
+
const filtered = results.filter(result => {
|
|
90
|
+
return isPromiseFailResult(result) === false
|
|
91
|
+
})
|
|
92
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
93
|
+
return filtered as V[]
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Giving a array, filter `PromiseFailResult` items, with extra `index` property
|
|
97
|
+
* which indicate the index of the item in the original array.
|
|
98
|
+
*/
|
|
99
|
+
export const promiseFilterFailResults = <V>(
|
|
100
|
+
results: Array<V | PromiseFailResult>,
|
|
101
|
+
): PromiseIndexedFailResult[] => {
|
|
102
|
+
const filtered = results.reduce<PromiseIndexedFailResult[]>((
|
|
103
|
+
accumulatedResults, currentResult, index
|
|
104
|
+
) => {
|
|
105
|
+
if (isPromiseFailResult(currentResult)) {
|
|
106
|
+
accumulatedResults.push({ ...currentResult, index })
|
|
107
|
+
}
|
|
108
|
+
return accumulatedResults
|
|
109
|
+
}, [])
|
|
110
|
+
return filtered
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface PromiseQueueFirstPromiseMakerContext<S = unknown> {
|
|
114
|
+
index: number
|
|
115
|
+
hasPreviousResult: false
|
|
116
|
+
state: S
|
|
117
|
+
}
|
|
118
|
+
interface PromiseQueueRestPromiseMakerContext<V, S = unknown> {
|
|
119
|
+
index: number
|
|
120
|
+
hasPreviousResult: true
|
|
121
|
+
previousResult: V | PromiseFailResult
|
|
122
|
+
state: S
|
|
123
|
+
}
|
|
124
|
+
type PromiseQueueFirstPromiseMaker<T, S = unknown> = (context: PromiseQueueFirstPromiseMakerContext<S>) => Promise<T>
|
|
125
|
+
type PromiseQueueRestPromiseMaker<T, S = unknown> = (context: PromiseQueueRestPromiseMakerContext<T, S>) => Promise<T>
|
|
126
|
+
type PromiseQueuePromiseMakers<T, S = unknown> = [
|
|
127
|
+
PromiseQueueFirstPromiseMaker<T, S>,
|
|
128
|
+
...PromiseQueueRestPromiseMaker<T, S>[]
|
|
129
|
+
]
|
|
130
|
+
export interface PromiseQueueOptions {
|
|
131
|
+
/**
|
|
132
|
+
* @default 0
|
|
133
|
+
*/
|
|
134
|
+
breakTime?: number
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Execute promise makers in sequence, passing the previous result to the next maker.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```
|
|
141
|
+
* // Expect: [1, 2]
|
|
142
|
+
* const example1 = await promiseQueue<number>([
|
|
143
|
+
* async () => 1,
|
|
144
|
+
* async ({ previousResult }) => (isPromiseFailResult(previousResult) ? 0 : previousResult + 1),
|
|
145
|
+
* ])
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export const promiseQueue = async <T, S = unknown>(
|
|
149
|
+
promiseMakers: PromiseQueuePromiseMakers<T, S>,
|
|
150
|
+
options?: PromiseQueueOptions | undefined,
|
|
151
|
+
): Promise<Array<T | PromiseFailResult>> => {
|
|
152
|
+
const { breakTime = 0 } = options ?? {}
|
|
153
|
+
const results: Array<T | PromiseFailResult> = []
|
|
154
|
+
|
|
155
|
+
let context: PromiseQueueFirstPromiseMakerContext<S> | PromiseQueueRestPromiseMakerContext<T, S> = {
|
|
156
|
+
index: 0,
|
|
157
|
+
hasPreviousResult: false,
|
|
158
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
159
|
+
state: undefined as unknown as S
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const [firstPromiseMaker, ...restPromiseMakers] = promiseMakers
|
|
163
|
+
const lastPromise = restPromiseMakers.reduce(async (
|
|
164
|
+
accumulatedPromise, currentPromiseMaker, index
|
|
165
|
+
) => {
|
|
166
|
+
return await accumulatedPromise
|
|
167
|
+
.catch(promiseCatchToFailResult)
|
|
168
|
+
.then(async (result) => {
|
|
169
|
+
results.push(result)
|
|
170
|
+
return await new Promise((resolve) => {
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
context = {
|
|
173
|
+
...context,
|
|
174
|
+
index: index + 1,
|
|
175
|
+
hasPreviousResult: true,
|
|
176
|
+
previousResult: result
|
|
177
|
+
}
|
|
178
|
+
resolve(currentPromiseMaker(context))
|
|
179
|
+
}, breakTime)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
}, firstPromiseMaker(context))
|
|
183
|
+
|
|
184
|
+
await lastPromise
|
|
185
|
+
.catch(promiseCatchToFailResult)
|
|
186
|
+
.then((result) => {
|
|
187
|
+
results.push(result)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
return results
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
interface PromiseRetryFirstPromiseMakerContext<S = unknown> {
|
|
194
|
+
time: number
|
|
195
|
+
hasPreviousResult: false
|
|
196
|
+
state: S
|
|
197
|
+
}
|
|
198
|
+
interface PromiseRetryRestPromiseMakerContext<T, S = unknown> {
|
|
199
|
+
time: number
|
|
200
|
+
hasPreviousResult: true
|
|
201
|
+
previousResult: T | PromiseFailResult
|
|
202
|
+
state: S
|
|
203
|
+
}
|
|
204
|
+
type PromiseRetryPromiseMaker<T, S = unknown> = (
|
|
205
|
+
context: PromiseRetryFirstPromiseMakerContext<S> | PromiseRetryRestPromiseMakerContext<T, S>
|
|
206
|
+
) => Promise<T>
|
|
207
|
+
export interface PromiseRetryOptions {
|
|
208
|
+
/**
|
|
209
|
+
* @default 0
|
|
210
|
+
*/
|
|
211
|
+
breakTime?: number
|
|
212
|
+
/**
|
|
213
|
+
* @default Infinity
|
|
214
|
+
*/
|
|
215
|
+
maxTryTimes?: number
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Retry while the predicate indicates the result is not acceptable.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```
|
|
222
|
+
* let counter = 0
|
|
223
|
+
* // Expect: 3
|
|
224
|
+
* const example1 = await promiseRetryWhile(
|
|
225
|
+
* (value) => value < 3,
|
|
226
|
+
* async () => {
|
|
227
|
+
* counter = counter + 1
|
|
228
|
+
* return counter
|
|
229
|
+
* },
|
|
230
|
+
* )
|
|
231
|
+
* ```
|
|
232
|
+
*
|
|
233
|
+
* @see {@link promiseRetryUntil}
|
|
234
|
+
*/
|
|
235
|
+
export const promiseRetryWhile = async <T, S = unknown>(
|
|
236
|
+
predicate: (value: T) => boolean | Promise<boolean>,
|
|
237
|
+
promiseMaker: PromiseRetryPromiseMaker<T, S>,
|
|
238
|
+
options?: PromiseRetryOptions | undefined,
|
|
239
|
+
): Promise<T | PromiseFailResult> => {
|
|
240
|
+
const {
|
|
241
|
+
breakTime = 0,
|
|
242
|
+
maxTryTimes = Infinity,
|
|
243
|
+
} = options ?? {}
|
|
244
|
+
|
|
245
|
+
if (maxTryTimes < 1) {
|
|
246
|
+
throw new Error("`maxTryTimes` must be greater than 0.")
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let time = 1
|
|
250
|
+
// oxlint-disable-next-line no-accumulating-spread
|
|
251
|
+
let context: PromiseRetryFirstPromiseMakerContext<S> | PromiseRetryRestPromiseMakerContext<T, S> = {
|
|
252
|
+
time,
|
|
253
|
+
hasPreviousResult: false,
|
|
254
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
255
|
+
state: undefined as unknown as S
|
|
256
|
+
}
|
|
257
|
+
let result = await promiseMaker(context).catch(promiseCatchToFailResult)
|
|
258
|
+
|
|
259
|
+
while (
|
|
260
|
+
(isPromiseFailResult(result) || (await predicate(result)))
|
|
261
|
+
&& time < maxTryTimes
|
|
262
|
+
) {
|
|
263
|
+
time = time + 1
|
|
264
|
+
// oxlint-disable-next-line no-loop-func
|
|
265
|
+
result = await new Promise<T | PromiseFailResult>((resolve) => {
|
|
266
|
+
setTimeout(() => {
|
|
267
|
+
context = {
|
|
268
|
+
...context,
|
|
269
|
+
time,
|
|
270
|
+
hasPreviousResult: true,
|
|
271
|
+
previousResult: result,
|
|
272
|
+
}
|
|
273
|
+
resolve(promiseMaker(context))
|
|
274
|
+
}, breakTime)
|
|
275
|
+
}).catch(promiseCatchToFailResult)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return result
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Retry until the predicate returns true for the current result.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```
|
|
286
|
+
* let counter = 0
|
|
287
|
+
* // Expect: 2
|
|
288
|
+
* const example1 = await promiseRetryUntil(
|
|
289
|
+
* (value) => value >= 2,
|
|
290
|
+
* async () => {
|
|
291
|
+
* counter = counter + 1
|
|
292
|
+
* return counter
|
|
293
|
+
* },
|
|
294
|
+
* )
|
|
295
|
+
* ```
|
|
296
|
+
*
|
|
297
|
+
* @see {@link promiseRetryWhile}
|
|
298
|
+
*/
|
|
299
|
+
export const promiseRetryUntil = async <T, S = unknown>(
|
|
300
|
+
predicate: (value: T, time: number) => boolean | Promise<boolean>,
|
|
301
|
+
promiseMaker: PromiseRetryPromiseMaker<T, S>,
|
|
302
|
+
options?: PromiseRetryOptions | undefined,
|
|
303
|
+
): Promise<T | PromiseFailResult> => {
|
|
304
|
+
const {
|
|
305
|
+
breakTime = 0,
|
|
306
|
+
maxTryTimes = Infinity,
|
|
307
|
+
} = options ?? {}
|
|
308
|
+
|
|
309
|
+
if (maxTryTimes < 1) {
|
|
310
|
+
throw new Error("`maxTryTimes` must be greater than 0.")
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let time = 1
|
|
314
|
+
// oxlint-disable-next-line no-accumulating-spread
|
|
315
|
+
let context: PromiseRetryFirstPromiseMakerContext<S> | PromiseRetryRestPromiseMakerContext<T, S> = {
|
|
316
|
+
time,
|
|
317
|
+
hasPreviousResult: false,
|
|
318
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
319
|
+
state: undefined as unknown as S
|
|
320
|
+
}
|
|
321
|
+
let result = await promiseMaker(context).catch(promiseCatchToFailResult)
|
|
322
|
+
|
|
323
|
+
while (
|
|
324
|
+
(isPromiseFailResult(result) || !(await predicate(result, time)))
|
|
325
|
+
&& time < maxTryTimes
|
|
326
|
+
) {
|
|
327
|
+
time = time + 1
|
|
328
|
+
// oxlint-disable-next-line no-loop-func
|
|
329
|
+
result = await new Promise<T | PromiseFailResult>((resolve) => {
|
|
330
|
+
setTimeout(() => {
|
|
331
|
+
context = {
|
|
332
|
+
...context,
|
|
333
|
+
time,
|
|
334
|
+
hasPreviousResult: true,
|
|
335
|
+
previousResult: result
|
|
336
|
+
}
|
|
337
|
+
resolve(promiseMaker(context))
|
|
338
|
+
}, breakTime)
|
|
339
|
+
}).catch(promiseCatchToFailResult)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return result
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Start periodic asynchronous execution and return a function to stop it.
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```
|
|
350
|
+
* const records: number[] = []
|
|
351
|
+
* const stop = promiseInterval(10, async (time) => {
|
|
352
|
+
* records.push(time)
|
|
353
|
+
* return time
|
|
354
|
+
* })
|
|
355
|
+
* // Later: stop()
|
|
356
|
+
* // Expect: typeof stop === "function"
|
|
357
|
+
* const example1 = typeof stop === "function"
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
export const promiseInterval = <T>(
|
|
361
|
+
interval: number,
|
|
362
|
+
promiseMaker: (time: number) => Promise<T>,
|
|
363
|
+
): (() => void) => {
|
|
364
|
+
let time = 1
|
|
365
|
+
|
|
366
|
+
const intervalID = setInterval(() => {
|
|
367
|
+
void promiseMaker(time)
|
|
368
|
+
time = time + 1
|
|
369
|
+
}, interval)
|
|
370
|
+
|
|
371
|
+
return () => {
|
|
372
|
+
clearInterval(intervalID)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
interface PromiseForeverFirstPromiseMakerContext<S = unknown> {
|
|
377
|
+
time: number
|
|
378
|
+
hasPreviousResult: false
|
|
379
|
+
state: S
|
|
380
|
+
}
|
|
381
|
+
interface PromiseForeverRestPromiseMakerContext<T, S = unknown> {
|
|
382
|
+
time: number
|
|
383
|
+
hasPreviousResult: true
|
|
384
|
+
previousResult: T | PromiseFailResult
|
|
385
|
+
state: S
|
|
386
|
+
}
|
|
387
|
+
type PromiseForeverPromiseMaker<T, S = unknown> = (
|
|
388
|
+
context: PromiseForeverFirstPromiseMakerContext<S> | PromiseForeverRestPromiseMakerContext<T, S>
|
|
389
|
+
) => Promise<T>
|
|
390
|
+
export interface PromiseForeverOptions {
|
|
391
|
+
/**
|
|
392
|
+
* @default 0
|
|
393
|
+
*/
|
|
394
|
+
breakTime?: number | undefined
|
|
395
|
+
onRejected?: ((time: number, result: PromiseFailResult) => void) | undefined
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Continuously execute an async maker forever with optional delay and rejection hook.
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```
|
|
402
|
+
* let times = 0
|
|
403
|
+
* promiseForever(async () => {
|
|
404
|
+
* times = times + 1
|
|
405
|
+
* return times
|
|
406
|
+
* }, { breakTime: 0 })
|
|
407
|
+
* // Expect: no return value
|
|
408
|
+
* const example1 = promiseForever(async () => 1)
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
export const promiseForever = <T, S = unknown>(
|
|
412
|
+
promiseMaker: PromiseForeverPromiseMaker<T, S>,
|
|
413
|
+
options?: PromiseForeverOptions | undefined,
|
|
414
|
+
): void => {
|
|
415
|
+
const {
|
|
416
|
+
breakTime = 0,
|
|
417
|
+
onRejected,
|
|
418
|
+
} = options ?? {}
|
|
419
|
+
|
|
420
|
+
let time = 1
|
|
421
|
+
let context: PromiseForeverFirstPromiseMakerContext<S> | PromiseForeverRestPromiseMakerContext<T, S> = {
|
|
422
|
+
time,
|
|
423
|
+
hasPreviousResult: false,
|
|
424
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
425
|
+
state: undefined as unknown as S
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const handlePromise = (result: T | PromiseFailResult): void => {
|
|
429
|
+
time = time + 1
|
|
430
|
+
setTimeout(() => {
|
|
431
|
+
context = {
|
|
432
|
+
...context,
|
|
433
|
+
time,
|
|
434
|
+
hasPreviousResult: true,
|
|
435
|
+
previousResult: result
|
|
436
|
+
}
|
|
437
|
+
runPromise(context)
|
|
438
|
+
}, breakTime)
|
|
439
|
+
}
|
|
440
|
+
const runPromise = (
|
|
441
|
+
context: PromiseForeverFirstPromiseMakerContext<S> | PromiseForeverRestPromiseMakerContext<T, S>
|
|
442
|
+
): void => {
|
|
443
|
+
void promiseMaker(context)
|
|
444
|
+
// oxlint-disable-next-line prefer-await-to-callbacks
|
|
445
|
+
.catch((error: unknown) => {
|
|
446
|
+
const failedResult = promiseCatchToFailResult(error)
|
|
447
|
+
if (onRejected !== undefined) {
|
|
448
|
+
try {
|
|
449
|
+
onRejected(time, failedResult)
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
// omit exceptions from execution of `onRejected`
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
console.log("❌ [promiseForever] unexcepted error occured:", failedResult)
|
|
457
|
+
}
|
|
458
|
+
return failedResult
|
|
459
|
+
})
|
|
460
|
+
.then(result => handlePromise(result))
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
runPromise(context)
|
|
464
|
+
}
|