forked from deco-cx/deco
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsync.ts
69 lines (64 loc) · 1.54 KB
/
sync.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
import { isAwaitable, type PromiseOrValue } from "../engine/core/utils.ts";
export interface SyncOnce<T> {
do: (cb: () => PromiseOrValue<T>) => PromiseOrValue<T>;
}
export class Mutex {
public locked: boolean;
public queue: Array<ReturnType<typeof Promise.withResolvers<void>>>;
constructor() {
this.locked = false;
this.queue = [];
}
acquire(): Promise<Disposable> {
const disposable = {
[Symbol.dispose]: () => {
return this.release();
},
};
if (!this.locked) {
this.locked = true;
return Promise.resolve(disposable);
}
const promise = Promise.withResolvers<void>();
this.queue.push(promise);
return promise.promise.then(() => {
return disposable;
});
}
freeOrNext(): boolean {
return !this.locked || this.queue.length === 0;
}
release() {
if (this.queue.length > 0) {
const next = this.queue.shift();
next?.resolve();
} else {
this.locked = false;
}
}
}
/**
* Run the function only once.
* usage:
*
* const runOnce = once<T>()
* runOnce.do(() => new Date()) // this will return always the first value used.
*/
export const once = <T>(): SyncOnce<T> => {
let result: PromiseOrValue<T> | null = null;
return {
do: (cb: () => PromiseOrValue<T>) => {
if (result !== null) {
return result;
}
const resp = cb();
if (isAwaitable(resp)) {
return result ??= resp.catch((err) => {
result = null;
throw err;
});
}
return result ??= resp;
},
};
};