@planet-matrix/mobius-model 0.3.0 → 0.5.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 +15 -0
- package/README.md +30 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +22 -4
- package/package.json +3 -3
- package/scripts/build.ts +4 -4
- package/src/basic/README.md +144 -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/enhance.ts +10 -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 +17 -0
- package/src/basic/is.ts +320 -0
- package/src/basic/number.ts +178 -0
- package/src/basic/object.ts +140 -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/encoding/README.md +105 -0
- package/src/encoding/base64.ts +98 -0
- package/src/encoding/index.ts +1 -0
- package/src/index.ts +4 -0
- package/src/random/README.md +109 -0
- package/src/random/index.ts +1 -0
- package/src/random/uuid.ts +103 -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 +46 -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/tests/unit/encoding/base64.spec.ts +40 -0
- package/tests/unit/random/uuid.spec.ts +37 -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,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
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { ReadableStreamController, ReadableStreamReadResult } from "node:stream/web"
|
|
2
|
+
|
|
3
|
+
import { ReadableStream } from "node:stream/web"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 从数组创建一个 ReadableStream。
|
|
7
|
+
*/
|
|
8
|
+
export const streamFromArray = <T>(values: T[]): ReadableStream<T> => {
|
|
9
|
+
const stream = new ReadableStream<T>({
|
|
10
|
+
start(controller): void {
|
|
11
|
+
for (const value of values) {
|
|
12
|
+
controller.enqueue(value)
|
|
13
|
+
}
|
|
14
|
+
controller.close()
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
return stream
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface StreamConsumeInSyncMacroTaskOptions<T> {
|
|
21
|
+
readableStream: ReadableStream<T>
|
|
22
|
+
onValue?: ((chunk: T) => (void | Promise<void>)) | undefined
|
|
23
|
+
onDone?: (() => (void | Promise<void>)) | undefined
|
|
24
|
+
onError?: ((error: Error) => (void | Promise<void>)) | undefined
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 在同步宏任务中完成 ReadableStream 的消费。
|
|
28
|
+
*/
|
|
29
|
+
export const streamConsumeInSyncMacroTask = async <T>(
|
|
30
|
+
options: StreamConsumeInSyncMacroTaskOptions<T>,
|
|
31
|
+
): Promise<void> => {
|
|
32
|
+
const { readableStream, onValue, onDone, onError } = options
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
for await (const chunk of readableStream) {
|
|
36
|
+
await onValue?.(chunk)
|
|
37
|
+
}
|
|
38
|
+
await onDone?.()
|
|
39
|
+
}
|
|
40
|
+
catch (exception) {
|
|
41
|
+
console.error(`Error reading stream: ${String(exception)}`)
|
|
42
|
+
await onError?.(new Error(`Error reading stream: ${String(exception)}`))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface StreamConsumeInAsyncMacroTaskOptions<T> {
|
|
47
|
+
readableStream: ReadableStream<T>
|
|
48
|
+
onValue?: ((chunk: T) => (void | Promise<void>)) | undefined
|
|
49
|
+
onDone?: (() => (void | Promise<void>)) | undefined
|
|
50
|
+
onError?: ((error: Error) => (void | Promise<void>)) | undefined
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 在异步宏任务中完成 ReadableStream 的消费。
|
|
54
|
+
*/
|
|
55
|
+
export const streamConsumeInAsyncMacroTask = <T>(
|
|
56
|
+
options: StreamConsumeInAsyncMacroTaskOptions<T>,
|
|
57
|
+
): void => {
|
|
58
|
+
const { readableStream, onValue, onDone, onError } = options
|
|
59
|
+
|
|
60
|
+
const reader = readableStream.getReader()
|
|
61
|
+
const read = (): void => {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
void reader.read()
|
|
64
|
+
.then(async (chunk) => {
|
|
65
|
+
const { done, value } = chunk
|
|
66
|
+
if (done === true) {
|
|
67
|
+
await onDone?.()
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
await onValue?.(value)
|
|
71
|
+
read()
|
|
72
|
+
})
|
|
73
|
+
.catch(async (reason: unknown) => {
|
|
74
|
+
await onError?.(new Error(String(reason)))
|
|
75
|
+
})
|
|
76
|
+
}, 0)
|
|
77
|
+
}
|
|
78
|
+
read()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface StreamTransformInAsyncMacroTaskOptions<T, U> {
|
|
82
|
+
readableStream?: ReadableStream<T> | undefined
|
|
83
|
+
reader?: ReadableStreamDefaultReader<T> | undefined
|
|
84
|
+
onChunk?: ((chunk: ReadableStreamReadResult<T>, controller: ReadableStreamDefaultController<U>) => (void | Promise<void>)) | undefined
|
|
85
|
+
onError?: ((error: Error) => (void | Error | Promise<void | Error>)) | undefined
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 在宏任务队列中完成 ReadableStream 到另一个 ReadableStream 的转换。
|
|
89
|
+
*/
|
|
90
|
+
export const streamTransformInAsyncMacroTask = <T, U>(
|
|
91
|
+
options: StreamTransformInAsyncMacroTaskOptions<T, U>,
|
|
92
|
+
): ReadableStream<U> => {
|
|
93
|
+
const { readableStream, reader, onChunk, onError } = options
|
|
94
|
+
|
|
95
|
+
if (reader === undefined && readableStream === undefined) {
|
|
96
|
+
throw new Error("Either readableStream or reader must be provided")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const preparedReader = reader ?? readableStream!.getReader()
|
|
100
|
+
const stream = new ReadableStream<U>({
|
|
101
|
+
start(controller): void {
|
|
102
|
+
const read = (): void => {
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
void preparedReader.read()
|
|
105
|
+
.then(async (chunk) => {
|
|
106
|
+
let shouldContinue = true
|
|
107
|
+
const proxyController: ReadableStreamController<U> = {
|
|
108
|
+
// oxlint-disable-next-line no-misused-spread
|
|
109
|
+
...controller,
|
|
110
|
+
enqueue: (chunk: U): void => {
|
|
111
|
+
controller.enqueue(chunk)
|
|
112
|
+
shouldContinue = true
|
|
113
|
+
},
|
|
114
|
+
close: (): void => {
|
|
115
|
+
controller.close()
|
|
116
|
+
shouldContinue = false
|
|
117
|
+
},
|
|
118
|
+
error: (error: Error): void => {
|
|
119
|
+
controller.error(error)
|
|
120
|
+
shouldContinue = false
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
await onChunk?.(chunk, proxyController)
|
|
124
|
+
if (shouldContinue === true) {
|
|
125
|
+
read()
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
.catch(async (reason: unknown) => {
|
|
129
|
+
const error = new Error(String(reason))
|
|
130
|
+
const refinedError = await onError?.(error)
|
|
131
|
+
controller.error(refinedError ?? error)
|
|
132
|
+
})
|
|
133
|
+
}, 0)
|
|
134
|
+
}
|
|
135
|
+
read()
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
return stream
|
|
140
|
+
}
|