-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
102 lines (87 loc) · 2.53 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
type PromiseResolve<T> = (value?: T | PromiseLike<T>) => void
function isSymbol(val: unknown): val is symbol {
return typeof val === 'symbol'
}
export default class PLoop {
readonly auto: boolean = true
#queue: PromiseResolve<any>[] = []
#queuing: boolean = false
#promising: boolean = false
#proxySet: WeakSet<Function> = new WeakSet()
#proxyMap: WeakMap<Function, Function> = new WeakMap()
next?: Function
nextAll?: Function
constructor(auto?: boolean) {
if (typeof auto === 'boolean')
this.auto = auto
if (!this.auto) {
this.next = this.#next
this.nextAll = () => {
while (this.#queuing) this.#next()
}
}
}
add(fn: Function) {
if (this.#proxySet.has(fn))
return fn
let proxyFn = this.#proxyMap.get(fn)
if (proxyFn)
return proxyFn
proxyFn = new Proxy(fn, {
apply: async (fnTarget, thisArg, argArray) => {
if (this.#promising)
return await Reflect.apply(fnTarget, thisArg, argArray)
let promise
if (this.#queuing) {
promise = new Promise((resolve: PromiseResolve<any>) => {
this.#queue.push(resolve)
}).then(() => Reflect.apply(fnTarget, thisArg, argArray))
}
else {
this.#queuing = true
promise = Promise.resolve(Reflect.apply(fnTarget, thisArg, argArray))
}
if (this.auto) {
promise.finally(() => {
setTimeout(() => {
this.#next()
}, 0)
})
}
return this.#proxyPromise(promise)
},
})
this.#proxySet.add(proxyFn)
this.#proxyMap.set(fn, proxyFn)
return proxyFn
}
#next() {
this.#promising = false
if (this.#queue.length > 0) {
const resolve = this.#queue.shift() as PromiseResolve<any>
resolve()
}
else {
this.#queuing = false
}
}
#proxyPromise(promise: Promise<any>) {
return new Proxy(promise, {
get: (target, p, receiver) => {
const thenable = Reflect.get(target, p, receiver)
if (isSymbol(p) || !['then', 'catch', 'finally'].includes(p))
return thenable
return (...args: Function[]) => {
return this.#proxyPromise(Reflect.apply(thenable, target, args.map((fn) => {
return new Proxy(fn, {
apply: async (fnTarget, thisArg, argArray) => {
this.#promising = true
return await Reflect.apply(fnTarget, thisArg, argArray)
},
})
})))
}
},
})
}
}