forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpending_tasks.ts
142 lines (131 loc) · 4.21 KB
/
pending_tasks.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
/**
* @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.dev/license
*/
import {BehaviorSubject} from 'rxjs';
import {inject} from './di/injector_compatibility';
import {ɵɵdefineInjectable} from './di/interface/defs';
import {OnDestroy} from './interface/lifecycle_hooks';
import {
ChangeDetectionScheduler,
NotificationSource,
} from './change_detection/scheduling/zoneless_scheduling';
/**
* Internal implementation of the pending tasks service.
*/
export class PendingTasksInternal implements OnDestroy {
private taskId = 0;
private pendingTasks = new Set<number>();
private get _hasPendingTasks() {
return this.hasPendingTasks.value;
}
hasPendingTasks = new BehaviorSubject<boolean>(false);
add(): number {
if (!this._hasPendingTasks) {
this.hasPendingTasks.next(true);
}
const taskId = this.taskId++;
this.pendingTasks.add(taskId);
return taskId;
}
has(taskId: number): boolean {
return this.pendingTasks.has(taskId);
}
remove(taskId: number): void {
this.pendingTasks.delete(taskId);
if (this.pendingTasks.size === 0 && this._hasPendingTasks) {
this.hasPendingTasks.next(false);
}
}
ngOnDestroy(): void {
this.pendingTasks.clear();
if (this._hasPendingTasks) {
this.hasPendingTasks.next(false);
}
}
/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({
token: PendingTasksInternal,
providedIn: 'root',
factory: () => new PendingTasksInternal(),
});
}
/**
* Service that keeps track of pending tasks contributing to the stableness of Angular
* application. While several existing Angular services (ex.: `HttpClient`) will internally manage
* tasks influencing stability, this API gives control over stability to library and application
* developers for specific cases not covered by Angular internals.
*
* The concept of stability comes into play in several important scenarios:
* - SSR process needs to wait for the application stability before serializing and sending rendered
* HTML;
* - tests might want to delay assertions until the application becomes stable;
*
* @usageNotes
* ```ts
* const pendingTasks = inject(PendingTasks);
* const taskCleanup = pendingTasks.add();
* // do work that should block application's stability and then:
* taskCleanup();
* ```
*
* @publicApi
* @developerPreview
*/
export class PendingTasks {
private internalPendingTasks = inject(PendingTasksInternal);
private scheduler = inject(ChangeDetectionScheduler);
/**
* Adds a new task that should block application's stability.
* @returns A cleanup function that removes a task when called.
*/
add(): () => void {
const taskId = this.internalPendingTasks.add();
return () => {
if (!this.internalPendingTasks.has(taskId)) {
// This pending task has already been cleared.
return;
}
// Notifying the scheduler will hold application stability open until the next tick.
this.scheduler.notify(NotificationSource.PendingTaskRemoved);
this.internalPendingTasks.remove(taskId);
};
}
/**
* Runs an asynchronous function and blocks the application's stability until the function completes.
*
* ```ts
* pendingTasks.run(async () => {
* const userData = await fetch('/api/user');
* this.userData.set(userData);
* });
* ```
*
* Application stability is at least delayed until the next tick after the `run` method resolves
* so it is safe to make additional updates to application state that would require UI synchronization:
*
* ```ts
* const userData = await pendingTasks.run(() => fetch('/api/user'));
* this.userData.set(userData);
* ```
*
* @param fn The asynchronous function to execute
*/
async run<T>(fn: () => Promise<T>): Promise<T> {
const removeTask = this.add();
try {
return await fn();
} finally {
removeTask();
}
}
/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({
token: PendingTasks,
providedIn: 'root',
factory: () => new PendingTasks(),
});
}