forked from facebookarchive/atom-ide-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
/
UniversalDisposable.js
123 lines (112 loc) · 3.23 KB
/
UniversalDisposable.js
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
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
export type IDestructible = {
destroy(): void,
onDidDestroy(callback: () => mixed): IDisposable,
};
export type AnyTeardown =
| (() => mixed)
| rxjs$ISubscription
| IDisposable
| IDestructible;
/**
* Like a CompositeDisposable, but in addition to Disposable instances it can
* also accept plain functions and Rx subscriptions.
*/
export default class UniversalDisposable {
disposed: boolean;
teardowns: Set<AnyTeardown>;
constructor(...teardowns: Array<AnyTeardown>) {
this.teardowns = new Set();
this.disposed = false;
if (teardowns.length) {
this.add(...teardowns);
}
}
add(...teardowns: Array<AnyTeardown>): void {
if (this.disposed) {
throw new Error('Cannot add to an already disposed UniversalDisposable!');
}
for (let i = 0; i < teardowns.length; i++) {
assertTeardown(teardowns[i]);
this.teardowns.add(teardowns[i]);
}
}
/**
* Adds a list of teardowns but also ties them to the lifetime of `destructible`.
* When `destructible` is destroyed (or `this.dispose()` gets called, whichever comes first),
* all `teardowns` provided are also disposed.
*
* This is a subtle pattern to get right because of two factors:
* - we need to make sure that all teardowns are also removed on destroy
* - we also need to ensure that we don't leak the onDidDestroy disposable
*/
addUntilDestroyed(
destructible: IDestructible,
...teardowns: Array<AnyTeardown>
) {
if (this.disposed) {
throw new Error('Cannot add to an already disposed UniversalDisposable!');
}
const destroyDisposable = new UniversalDisposable(
...teardowns,
destructible.onDidDestroy(() => {
destroyDisposable.dispose();
this.remove(destroyDisposable);
}),
);
this.add(destroyDisposable);
}
remove(teardown: AnyTeardown): void {
if (!this.disposed) {
this.teardowns.delete(teardown);
}
}
dispose(): void {
if (!this.disposed) {
this.disposed = true;
this.teardowns.forEach(teardown => {
if (typeof teardown.dispose === 'function') {
teardown.dispose();
} else if (typeof teardown.unsubscribe === 'function') {
teardown.unsubscribe();
} else if (typeof teardown.destroy === 'function') {
teardown.destroy();
} else if (typeof teardown === 'function') {
teardown();
}
});
this.teardowns = (null: any);
}
}
unsubscribe(): void {
this.dispose();
}
clear(): void {
if (!this.disposed) {
this.teardowns.clear();
}
}
}
function assertTeardown(teardown: AnyTeardown): void {
if (
typeof teardown.dispose === 'function' ||
typeof teardown.unsubscribe === 'function' ||
typeof teardown.destroy === 'function' ||
typeof teardown === 'function'
) {
return;
}
throw new TypeError(
'Arguments to UniversalDisposable.add must be disposable',
);
}