-
Notifications
You must be signed in to change notification settings - Fork 2
/
explicit-resource-management.ts
162 lines (130 loc) · 3.74 KB
/
explicit-resource-management.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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// An implementation of the ECMAScript Explicit Resource Management proposal.
export interface DisposableResource {
[Symbol.dispose](): void
}
export interface AsyncDisposableResource {
[Symbol.asyncDispose](): void
}
const dispose = ((Symbol as any).dispose ??= Symbol("Symbol.dispose"))
const asyncDispose = ((Symbol as any).asyncDispose ??= Symbol(
"Symbol.asyncDispose",
))
let disposables: unknown[] | undefined
export function usingBlock<T>(fn: () => T): T {
let value: T
const oldDisposables = disposables
const mustBeDisposed: unknown[] = (disposables = [])
try {
value = fn()
} finally {
disposables = oldDisposables
const errors: unknown[] = []
let shouldThrow = false
for (const disposable of mustBeDisposed) {
try {
if (
(typeof disposable != "object" && typeof disposable != "function") ||
disposable == null
) {
throw new TypeError(
"A disposable was of an improper type: " + typeof disposable,
)
}
const disposeFn: unknown = (disposable as any)[dispose]
if (typeof disposeFn != "function") {
throw new TypeError("A disposable is missing an @@dispose method.")
}
disposeFn.call(disposable)
} catch (error) {
errors.push(error)
}
}
if (shouldThrow) {
throw new AggregateError(errors)
}
}
return value
}
export async function asyncUsingBlock<T>(fn: () => T): Promise<Awaited<T>> {
let value: Awaited<T>
const oldDisposables = disposables
const mustBeDisposed: unknown[] = (disposables = [])
try {
value = await fn()
} finally {
disposables = oldDisposables
const errors: unknown[] = []
let shouldThrow = false
for (const disposable of mustBeDisposed) {
try {
if (
(typeof disposable != "object" && typeof disposable != "function") ||
disposable == null
) {
throw new TypeError(
"A disposable was of an improper type: " + typeof disposable,
)
}
const asyncDisposeFn: unknown = (disposable as any)[asyncDispose]
if (typeof asyncDisposeFn == "function") {
asyncDisposeFn.call(disposable)
} else {
const disposeFn: unknown = (disposable as any)[dispose]
if (typeof disposeFn != "function") {
throw new TypeError(
"An async disposable is missing an @@asyncDispose or @@dispose method.",
)
}
disposeFn.call(disposable)
}
} catch (error) {
errors.push(error)
}
}
if (shouldThrow) {
throw new AggregateError(errors)
}
}
return value
}
export function using<T extends DisposableResource | null | undefined>(
disposable: T,
): T {
if (!disposables) {
throw new Error("Cannot call 'using' outside of a using {} block.")
}
disposables.push(disposable)
return disposable
}
export function asyncUsing<
T extends AsyncDisposableResource | DisposableResource | null | undefined,
>(disposable: T): T {
if (!disposables) {
throw new Error("Cannot call 'usingAsync' outside of a using {} block.")
}
disposables.push(disposable)
return disposable
}
export class Disposable {
static from(disposables: Iterable<DisposableResource | null | undefined>) {
disposables = [...disposables]
return new Disposable(() => {
for (const disposable of disposables) {
disposable?.[Symbol.dispose]()
}
})
}
#dispose: () => void
constructor(dispose: () => void) {
this.#dispose = dispose
}
[Symbol.dispose]() {
this.#dispose()
}
}
declare global {
interface SymbolConstructor {
readonly dispose: unique symbol
readonly asyncDispose: unique symbol
}
}