-
Notifications
You must be signed in to change notification settings - Fork 54
/
freeable.ts
73 lines (65 loc) · 2.24 KB
/
freeable.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
import { Freeable } from './types';
import { isPromise } from './isPromise';
/** A scope to ease the management of objects that require manual resource management. */
export class ManagedFreeableScope {
#scopeStack: Freeable[] = [];
#disposed = false;
/**
* Objects passed to this method will then be managed by the instance.
*
* @param freeable An object with a free function, or undefined. This makes it suitable for wrapping functions that
* may or may not return a value, to minimise the implementation logic.
* @returns The freeable object passed in, which can be undefined.
*/
public manage<T extends Freeable | undefined>(freeable: T): T {
if (freeable === undefined) return freeable;
if (this.#disposed) throw new Error('This scope is already disposed.');
this.#scopeStack.push(freeable);
return freeable;
}
/** Once the freeable objects being managed are no longer being accessed, call this method. */
public dispose(): void {
if (this.#disposed) return;
for (const resource of this.#scopeStack) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((resource as any)?.ptr === 0 || !resource?.free) {
continue;
}
resource?.free();
}
this.#disposed = true;
}
}
class AutoFree<TReturn> {
#scope: ManagedFreeableScope;
readonly #callback: (scope: ManagedFreeableScope) => TReturn;
constructor(cb: (scope: ManagedFreeableScope) => TReturn) {
this.#callback = cb;
this.#scope = new ManagedFreeableScope();
}
public execute() {
let result: TReturn;
try {
result = this.#callback(this.#scope);
if (isPromise(result)) {
return result
.then((value) => {
this.#scope.dispose();
return value;
})
.catch((error) => {
this.#scope.dispose();
throw error;
}) as TReturn;
}
this.#scope.dispose();
return result;
} catch (error) {
this.#scope.dispose();
throw error;
}
}
}
/** A wrapper function to setup and dispose of a ManagedFreeableScope at the end of the callback execution. */
export const usingAutoFree = <TReturn>(cb: (scope: ManagedFreeableScope) => TReturn) =>
new AutoFree<TReturn>(cb).execute();