Skip to content

Commit 1b24707

Browse files
committed
refactor: Replace Zone.scheduleMacroTask with ExperimentalPendingTasks
1 parent 19d3a34 commit 1b24707

File tree

1 file changed

+29
-51
lines changed

1 file changed

+29
-51
lines changed

src/zones.ts

Lines changed: 29 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
/* eslint-disable @typescript-eslint/ban-ts-comment */
2-
import { Injectable, NgZone } from "@angular/core";
2+
import {
3+
ExperimentalPendingTasks,
4+
Injectable,
5+
NgZone,
6+
inject,
7+
} from "@angular/core";
38
import {
49
Observable,
510
Operator,
@@ -48,17 +53,16 @@ export class ɵZoneScheduler implements SchedulerLike {
4853
}
4954

5055
class BlockUntilFirstOperator<T> implements Operator<T, T> {
51-
// @ts-ignore
52-
private task: MacroTask | null = null;
53-
54-
constructor(private zone: any) {}
56+
constructor(
57+
private zone: any,
58+
private pendingTasks: ExperimentalPendingTasks
59+
) {}
5560

5661
call(subscriber: Subscriber<T>, source: Observable<T>): TeardownLogic {
57-
const unscheduleTask = this.unscheduleTask.bind(this);
58-
// @ts-ignore
59-
this.task = this.zone.run(() =>
60-
Zone.current.scheduleMacroTask("firebaseZoneBlock", noop, {}, noop, noop)
61-
);
62+
const taskDone = this.zone.run(() => this.pendingTasks.add());
63+
// maybe this is a race condition, invoke in a timeout
64+
// hold for 10ms while I try to figure out what is going on
65+
const unscheduleTask = () => setTimeout(taskDone, 10);
6266

6367
return source
6468
.pipe(
@@ -71,17 +75,6 @@ class BlockUntilFirstOperator<T> implements Operator<T, T> {
7175
.subscribe(subscriber)
7276
.add(unscheduleTask);
7377
}
74-
75-
private unscheduleTask() {
76-
// maybe this is a race condition, invoke in a timeout
77-
// hold for 10ms while I try to figure out what is going on
78-
setTimeout(() => {
79-
if (this.task != null && this.task.state === "scheduled") {
80-
this.task.invoke();
81-
this.task = null;
82-
}
83-
}, 10);
84-
}
8578
}
8679

8780
@Injectable({
@@ -90,14 +83,15 @@ class BlockUntilFirstOperator<T> implements Operator<T, T> {
9083
export class ɵAngularFireSchedulers {
9184
public readonly outsideAngular: ɵZoneScheduler;
9285
public readonly insideAngular: ɵZoneScheduler;
86+
public readonly pendingTasks = inject(ExperimentalPendingTasks);
9387

9488
constructor(public ngZone: NgZone) {
95-
// @ts-ignore
9689
this.outsideAngular = ngZone.runOutsideAngular(
90+
// @ts-ignore
9791
() => new ɵZoneScheduler(Zone.current)
9892
);
99-
// @ts-ignore
10093
this.insideAngular = ngZone.run(
94+
// @ts-ignore
10195
() => new ɵZoneScheduler(Zone.current, asyncScheduler)
10296
);
10397
globalThis.ɵAngularFireScheduler ||= this;
@@ -149,7 +143,9 @@ export function ɵkeepUnstableUntilFirstFactory(
149143
return function keepUnstableUntilFirst<T>(
150144
obs$: Observable<T>
151145
): Observable<T> {
152-
obs$ = obs$.lift(new BlockUntilFirstOperator(schedulers.ngZone));
146+
obs$ = obs$.lift(
147+
new BlockUntilFirstOperator(schedulers.ngZone, schedulers.pendingTasks)
148+
);
153149

154150
return obs$.pipe(
155151
// Run the subscribe body outside of Angular (e.g. calling Firebase SDK to add a listener to a change event)
@@ -165,19 +161,15 @@ export function ɵkeepUnstableUntilFirstFactory(
165161
// @ts-ignore
166162
const zoneWrapFn = (
167163
it: (...args: any[]) => any,
168-
macrotask: MacroTask | undefined
164+
taskDone: VoidFunction | undefined
169165
) => {
170166
// eslint-disable-next-line @typescript-eslint/no-this-alias
171167
const _this = this;
172168
// function() is needed for the arguments object
173169
return function () {
174170
const _arguments = arguments;
175-
if (macrotask) {
176-
setTimeout(() => {
177-
if (macrotask.state === "scheduled") {
178-
macrotask.invoke();
179-
}
180-
}, 10);
171+
if (taskDone) {
172+
setTimeout(taskDone, 10);
181173
}
182174
return run(() => it.apply(_this, _arguments));
183175
};
@@ -186,27 +178,17 @@ const zoneWrapFn = (
186178
export const ɵzoneWrap = <T = unknown>(it: T, blockUntilFirst: boolean): T => {
187179
// function() is needed for the arguments object
188180
return function () {
189-
// @ts-ignore
190-
let macrotask: MacroTask | undefined;
181+
let taskDone: VoidFunction | undefined;
191182
const _arguments = arguments;
192-
// if this is a callback function, e.g, onSnapshot, we should create a microtask and invoke it
183+
// if this is a callback function, e.g, onSnapshot, we should create a pending task and complete it
193184
// only once one of the callback functions is tripped.
194185
for (let i = 0; i < arguments.length; i++) {
195186
if (typeof _arguments[i] === "function") {
196187
if (blockUntilFirst) {
197-
// @ts-ignore
198-
macrotask ||= run(() =>
199-
Zone.current.scheduleMacroTask(
200-
"firebaseZoneBlock",
201-
noop,
202-
{},
203-
noop,
204-
noop
205-
)
206-
);
188+
taskDone ||= run(() => getSchedulers().pendingTasks.add());
207189
}
208190
// TODO create a microtask to track callback functions
209-
_arguments[i] = zoneWrapFn(_arguments[i], macrotask);
191+
_arguments[i] = zoneWrapFn(_arguments[i], taskDone);
210192
}
211193
}
212194
const ret = runOutsideAngular(() => (it as any).apply(this, _arguments));
@@ -234,15 +216,11 @@ export const ɵzoneWrap = <T = unknown>(it: T, blockUntilFirst: boolean): T => {
234216
)
235217
)
236218
);
237-
} else if (typeof ret === "function" && macrotask) {
219+
} else if (typeof ret === "function" && taskDone) {
238220
// Handle unsubscribe
239221
// function() is needed for the arguments object
240222
return function () {
241-
setTimeout(() => {
242-
if (macrotask && macrotask.state === "scheduled") {
243-
macrotask.invoke();
244-
}
245-
}, 10);
223+
setTimeout(taskDone, 10);
246224
return ret.apply(this, arguments);
247225
};
248226
} else {

0 commit comments

Comments
 (0)