Skip to content

Commit c740189

Browse files
committed
refactor: Replace Zone.scheduleMacroTask with ExperimentalPendingTasks
1 parent 3639e41 commit c740189

File tree

3 files changed

+60
-57
lines changed

3 files changed

+60
-57
lines changed

src/compat/angularfire2.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CompilerFactory, DoBootstrap, NgModule, NgZone, PlatformRef } from '@angular/core';
1+
import { CompilerFactory, DoBootstrap, ExperimentalPendingTasks, NgModule, NgZone, PlatformRef } from '@angular/core';
22
import { TestBed } from '@angular/core/testing';
33
import { ɵAngularFireSchedulers, ɵZoneScheduler, ɵkeepUnstableUntilFirstFactory } from '@angular/fire';
44
import { AngularFireModule, FirebaseApp } from '@angular/fire/compat';
@@ -89,7 +89,7 @@ describe('angularfire', () => {
8989
let schedulers: ɵAngularFireSchedulers;
9090
let outsideZone: Zone;
9191
let insideZone: Zone;
92-
beforeAll(() => {
92+
beforeEach(() => {
9393
outsideZone = Zone.current;
9494
insideZone = Zone.current.fork({
9595
name: 'ngZone'
@@ -100,7 +100,7 @@ describe('angularfire', () => {
100100
runOutsideAngular: outsideZone.runGuarded.bind(outsideZone),
101101
runTask: insideZone.run.bind(insideZone)
102102
} as NgZone;
103-
schedulers = new ɵAngularFireSchedulers(ngZone);
103+
schedulers = new ɵAngularFireSchedulers(ngZone, TestBed.inject(ExperimentalPendingTasks));
104104
});
105105

106106
it('should re-schedule emissions asynchronously', done => {
@@ -157,7 +157,8 @@ describe('angularfire', () => {
157157
runTask: insideZone.run.bind(insideZone)
158158
} as NgZone,
159159
outsideAngular: new ɵZoneScheduler(outsideZone, testScheduler),
160-
insideAngular: new ɵZoneScheduler(insideZone, testScheduler)
160+
insideAngular: new ɵZoneScheduler(insideZone, testScheduler),
161+
pendingTasks: TestBed.inject(ExperimentalPendingTasks),
161162
};
162163
const keepUnstableOp = ɵkeepUnstableUntilFirstFactory(trackingSchedulers);
163164

src/compat/database/database.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { NgZone } from '@angular/core';
1+
import { ExperimentalPendingTasks, NgZone } from '@angular/core';
22
import { TestBed } from '@angular/core/testing';
33
import { ɵAngularFireSchedulers } from '@angular/fire';
44
import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire/compat';
55
import { AngularFireDatabase, AngularFireDatabaseModule, URL } from '@angular/fire/compat/database';
6+
import 'firebase/compat/database';
67
import { COMMON_CONFIG } from '../../../src/test-config';
78
import { rando } from '../../../src/utils';
8-
import 'firebase/compat/database';
99

1010
describe('AngularFireDatabase', () => {
1111
let app: FirebaseApp;
@@ -41,7 +41,7 @@ describe('AngularFireDatabase', () => {
4141
});
4242

4343
it('should accept a Firebase App in the constructor', (done) => {
44-
const schedulers = new ɵAngularFireSchedulers(zone);
44+
const schedulers = new ɵAngularFireSchedulers(zone, TestBed.inject(ExperimentalPendingTasks));
4545
const database = new AngularFireDatabase(
4646
app.options, rando(), undefined, {}, zone, schedulers, undefined, undefined,
4747
undefined, undefined, undefined, undefined, undefined, undefined, undefined,

src/zones.ts

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
/* eslint-disable @typescript-eslint/ban-ts-comment */
2-
import { Injectable, NgZone } from '@angular/core';
2+
import {
3+
ExperimentalPendingTasks,
4+
Injectable,
5+
NgZone
6+
} from "@angular/core";
37
import {
48
Observable,
59
Operator,
@@ -46,32 +50,21 @@ export class ɵZoneScheduler implements SchedulerLike {
4650
}
4751

4852
class BlockUntilFirstOperator<T> implements Operator<T, T> {
49-
// @ts-ignore
50-
private task: MacroTask | null = null;
51-
52-
constructor(private zone: any) {
53-
}
53+
constructor(
54+
private zone: any,
55+
private pendingTasks: ExperimentalPendingTasks
56+
) {}
5457

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

6064
return source.pipe(
6165
tap({ next: unscheduleTask, complete: unscheduleTask, error: unscheduleTask })
6266
).subscribe(subscriber).add(unscheduleTask);
6367
}
64-
65-
private unscheduleTask() {
66-
// maybe this is a race condition, invoke in a timeout
67-
// hold for 10ms while I try to figure out what is going on
68-
setTimeout(() => {
69-
if (this.task != null && this.task.state === 'scheduled') {
70-
this.task.invoke();
71-
this.task = null;
72-
}
73-
}, 10);
74-
}
7568
}
7669

7770
@Injectable({
@@ -81,11 +74,15 @@ export class ɵAngularFireSchedulers {
8174
public readonly outsideAngular: ɵZoneScheduler;
8275
public readonly insideAngular: ɵZoneScheduler;
8376

84-
constructor(public ngZone: NgZone) {
85-
// @ts-ignore
86-
this.outsideAngular = ngZone.runOutsideAngular(() => new ɵZoneScheduler(Zone.current));
87-
// @ts-ignore
88-
this.insideAngular = ngZone.run(() => new ɵZoneScheduler(Zone.current, asyncScheduler));
77+
constructor(public ngZone: NgZone, public pendingTasks: ExperimentalPendingTasks) {
78+
this.outsideAngular = ngZone.runOutsideAngular(
79+
// @ts-ignore
80+
() => new ɵZoneScheduler(Zone.current)
81+
);
82+
this.insideAngular = ngZone.run(
83+
// @ts-ignore
84+
() => new ɵZoneScheduler(Zone.current, asyncScheduler)
85+
);
8986
globalThis.ɵAngularFireScheduler ||= this;
9087
}
9188
}
@@ -126,10 +123,14 @@ export function keepUnstableUntilFirst<T>(obs$: Observable<T>): Observable<T> {
126123
* value from firebase but doesn't block the zone forever since the firebase subscription
127124
* is still alive.
128125
*/
129-
export function ɵkeepUnstableUntilFirstFactory(schedulers: ɵAngularFireSchedulers) {
130-
return function keepUnstableUntilFirst<T>(obs$: Observable<T>): Observable<T> {
126+
export function ɵkeepUnstableUntilFirstFactory(
127+
schedulers: ɵAngularFireSchedulers
128+
) {
129+
return function keepUnstableUntilFirst<T>(
130+
obs$: Observable<T>
131+
): Observable<T> {
131132
obs$ = obs$.lift(
132-
new BlockUntilFirstOperator(schedulers.ngZone)
133+
new BlockUntilFirstOperator(schedulers.ngZone, schedulers.pendingTasks)
133134
);
134135

135136
return obs$.pipe(
@@ -144,39 +145,36 @@ export function ɵkeepUnstableUntilFirstFactory(schedulers: ɵAngularFireSchedul
144145
}
145146

146147
// @ts-ignore
147-
const zoneWrapFn = (it: (...args: any[]) => any, macrotask: MacroTask|undefined) => {
148+
const zoneWrapFn = (
149+
it: (...args: any[]) => any,
150+
taskDone: VoidFunction | undefined
151+
) => {
148152
// eslint-disable-next-line @typescript-eslint/no-this-alias
149153
const _this = this;
150154
// function() is needed for the arguments object
151155
return function() {
152156
const _arguments = arguments;
153-
if (macrotask) {
154-
setTimeout(() => {
155-
if (macrotask.state === 'scheduled') {
156-
macrotask.invoke();
157-
}
158-
}, 10);
157+
if (taskDone) {
158+
setTimeout(taskDone, 10);
159159
}
160160
return run(() => it.apply(_this, _arguments));
161161
};
162162
};
163163

164164
export const ɵzoneWrap = <T= unknown>(it: T, blockUntilFirst: boolean): T => {
165165
// function() is needed for the arguments object
166-
return function() {
167-
// @ts-ignore
168-
let macrotask: MacroTask | undefined;
166+
return function () {
167+
let taskDone: VoidFunction | undefined;
169168
const _arguments = arguments;
170-
// if this is a callback function, e.g, onSnapshot, we should create a microtask and invoke it
169+
// if this is a callback function, e.g, onSnapshot, we should create a pending task and complete it
171170
// only once one of the callback functions is tripped.
172171
for (let i = 0; i < arguments.length; i++) {
173172
if (typeof _arguments[i] === 'function') {
174173
if (blockUntilFirst) {
175-
// @ts-ignore
176-
macrotask ||= run(() => Zone.current.scheduleMacroTask('firebaseZoneBlock', noop, {}, noop, noop));
174+
taskDone ||= run(() => getSchedulers().pendingTasks.add());
177175
}
178176
// TODO create a microtask to track callback functions
179-
_arguments[i] = zoneWrapFn(_arguments[i], macrotask);
177+
_arguments[i] = zoneWrapFn(_arguments[i], taskDone);
180178
}
181179
}
182180
const ret = runOutsideAngular(() => (it as any).apply(this, _arguments));
@@ -195,16 +193,20 @@ export const ɵzoneWrap = <T= unknown>(it: T, blockUntilFirst: boolean): T => {
195193
return ret.pipe(keepUnstableUntilFirst) as any;
196194
} else if (ret instanceof Promise) {
197195
// eslint-disable-next-line @typescript-eslint/no-misused-promises
198-
return run(() => new Promise((resolve, reject) => ret.then(it => run(() => resolve(it)), reason => run(() => reject(reason)))));
199-
} else if (typeof ret === 'function' && macrotask) {
196+
return run(
197+
() =>
198+
new Promise((resolve, reject) =>
199+
ret.then(
200+
(it) => run(() => resolve(it)),
201+
(reason) => run(() => reject(reason))
202+
)
203+
)
204+
);
205+
} else if (typeof ret === "function" && taskDone) {
200206
// Handle unsubscribe
201207
// function() is needed for the arguments object
202-
return function() {
203-
setTimeout(() => {
204-
if (macrotask && macrotask.state === 'scheduled') {
205-
macrotask.invoke();
206-
}
207-
}, 10);
208+
return function () {
209+
setTimeout(taskDone, 10);
208210
return ret.apply(this, arguments);
209211
};
210212
} else {

0 commit comments

Comments
 (0)