-
Notifications
You must be signed in to change notification settings - Fork 24.8k
/
watch.ts
150 lines (128 loc) Β· 3.82 KB
/
watch.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
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
consumerAfterComputation,
consumerBeforeComputation,
consumerDestroy,
consumerMarkDirty,
consumerPollProducersForChange,
isInNotificationPhase,
REACTIVE_NODE,
ReactiveNode,
SIGNAL,
} from './graph';
/**
* A cleanup function that can be optionally registered from the watch logic. If registered, the
* cleanup logic runs before the next watch execution.
*/
export type WatchCleanupFn = () => void;
/**
* A callback passed to the watch function that makes it possible to register cleanup logic.
*/
export type WatchCleanupRegisterFn = (cleanupFn: WatchCleanupFn) => void;
export interface Watch {
notify(): void;
/**
* Execute the reactive expression in the context of this `Watch` consumer.
*
* Should be called by the user scheduling algorithm when the provided
* `schedule` hook is called by `Watch`.
*/
run(): void;
cleanup(): void;
/**
* Destroy the watcher:
* - disconnect it from the reactive graph;
* - mark it as destroyed so subsequent run and notify operations are noop.
*/
destroy(): void;
[SIGNAL]: WatchNode;
}
export interface WatchNode extends ReactiveNode {
hasRun: boolean;
fn: ((onCleanup: WatchCleanupRegisterFn) => void) | null;
schedule: ((watch: Watch) => void) | null;
cleanupFn: WatchCleanupFn;
ref: Watch;
}
export function createWatch(
fn: (onCleanup: WatchCleanupRegisterFn) => void,
schedule: (watch: Watch) => void,
allowSignalWrites: boolean,
): Watch {
const node: WatchNode = Object.create(WATCH_NODE);
if (allowSignalWrites) {
node.consumerAllowSignalWrites = true;
}
node.fn = fn;
node.schedule = schedule;
const registerOnCleanup = (cleanupFn: WatchCleanupFn) => {
node.cleanupFn = cleanupFn;
};
function isWatchNodeDestroyed(node: WatchNode) {
return node.fn === null && node.schedule === null;
}
function destroyWatchNode(node: WatchNode) {
if (!isWatchNodeDestroyed(node)) {
consumerDestroy(node); // disconnect watcher from the reactive graph
node.cleanupFn();
// nullify references to the integration functions to mark node as destroyed
node.fn = null;
node.schedule = null;
node.cleanupFn = NOOP_CLEANUP_FN;
}
}
const run = () => {
if (node.fn === null) {
// trying to run a destroyed watch is noop
return;
}
if (isInNotificationPhase()) {
throw new Error(`Schedulers cannot synchronously execute watches while scheduling.`);
}
node.dirty = false;
if (node.hasRun && !consumerPollProducersForChange(node)) {
return;
}
node.hasRun = true;
const prevConsumer = consumerBeforeComputation(node);
try {
node.cleanupFn();
node.cleanupFn = NOOP_CLEANUP_FN;
node.fn(registerOnCleanup);
} finally {
consumerAfterComputation(node, prevConsumer);
}
};
node.ref = {
notify: () => consumerMarkDirty(node),
run,
cleanup: () => node.cleanupFn(),
destroy: () => destroyWatchNode(node),
[SIGNAL]: node,
};
return node.ref;
}
const NOOP_CLEANUP_FN: WatchCleanupFn = () => {};
// Note: Using an IIFE here to ensure that the spread assignment is not considered
// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
// TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
const WATCH_NODE: Partial<WatchNode> = /* @__PURE__ */ (() => {
return {
...REACTIVE_NODE,
consumerIsAlwaysLive: true,
consumerAllowSignalWrites: false,
consumerMarkedDirty: (node: WatchNode) => {
if (node.schedule !== null) {
node.schedule(node.ref);
}
},
hasRun: false,
cleanupFn: NOOP_CLEANUP_FN,
};
})();