0 Tage Serie
javascript / expert
Snippet

Deterministische Teardown-Reihenfolge in verschachtelten DestroyRef-Hierarchien

`DestroyRef.onDestroy`-Callbacks laufen pro Injector in Registrierungsreihenfolge, aber zur Laufzeit dynamisch registrierte Ressourcen — etwa Sockets, die von lazy geladenen Feature-Komponenten geöffnet werden — brauchen einen Teardown in umgekehrter Erwerbsreihenfolge, um nicht eine gemeinsam genutzte Abhängigkeit zu schließen, bevor das Handle, das noch darauf verweist, geschlossen ist. Dieser Pool verfolgt die Einfügereihenfolge in einer `Map` und baut in umgekehrter Reihenfolge ab; das `closing`-Flag schließt alles, was mitten im Teardown registriert wird, sofort, statt es in eine Menge zu legen, die nie wieder durchlaufen wird. Ohne das Flag könnte eine Komponente, die durch ihr eigenes `ngOnDestroy` während der Teardown-Schleife des Pools zerstört wird, ein Handle registrieren, das stillschweigend nie geschlossen wird.

snippet.js
javascript
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
import { Injectable, DestroyRef, inject } from '@angular/core';
@Injectable()
export class ResourcePool {
private handles = new Map<string, { close(): void }>();
private closing = false;
private destroyRef = inject(DestroyRef);
constructor() {
this.destroyRef.onDestroy(() => this.teardown());
}
register(key: string, handle: { close(): void }): void {
if (this.closing) {
handle.close();
return;
}
this.handles.get(key)?.close();
this.handles.set(key, handle);
}
private teardown(): void {
this.closing = true;
for (const [key, handle] of [...this.handles].reverse()) {
handle.close();
this.handles.delete(key);
}
}
}
angular
Erklärung
1
this.destroyRef.onDestroy(() => this.teardown());
Bindet das Cleanup an den Lebenszyklus des Injectors statt an das `ngOnDestroy` einer Komponente, sodass der Pool auch auf Route- oder Lazy-Loading-Injector-Ebene korrekt abgebaut wird.
2
for (const [key, handle] of [...this.handles].reverse()) {
Erstellt vor dem Umkehren einen Array-Schnappschuss der Einträge, da das Verändern einer `Map` während der Iteration über ihren eigenen aktiven Iterator zu undefinierter Reihenfolge führen kann.
3
if (this.closing) { handle.close(); return; }
Fängt Registrierungen ab, die nach Beginn des Teardowns eintreffen, und schließt sie sofort, sodass sie nie ungeschlossen in einer `Map` verbleiben, die niemand mehr leert.