refactor(core): use native Promise.withResolvers() in remaining tests

Replaces the remaining hand-rolled deferred promise implementations
with the native `Promise.withResolvers()` API and removes the now
unused helper and import.

Follow-up to #69739.
This commit is contained in:
Shuaib Hasan Akib
2026-09-06 23:40:09 +06:00
committed by Kristiyan Kostadinov
parent 49a797f510
commit 1bc7e3c2c3
19 changed files with 70 additions and 149 deletions
+11 -10
View File
@@ -767,15 +767,12 @@ describe('FetchBackend', () => {
export class MockFetchFactory extends FetchFactory {
public readonly response = new MockFetchResponse();
public readonly request = new MockFetchRequest();
private resolve!: Function;
private reject!: Function;
private resolve!: (value: Response | PromiseLike<Response>) => void;
private reject!: (reason?: any) => void;
private clearWarningTimeout?: VoidFunction;
private promise = new Promise<Response>((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
private promise = this.createFetchPromise();
override fetch = (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
this.request.method = init?.method;
@@ -849,10 +846,14 @@ export class MockFetchFactory extends FetchFactory {
}
resetFetchPromise() {
this.promise = new Promise<Response>((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
this.promise = this.createFetchPromise();
}
private createFetchPromise(): Promise<Response> {
const {promise, resolve, reject} = Promise.withResolvers<Response>();
this.resolve = resolve;
this.reject = reject;
return promise;
}
}
@@ -178,10 +178,7 @@ describe('AsyncPipe', () => {
let promise: Promise<any>;
beforeEach(() => {
promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
({promise, resolve, reject} = Promise.withResolvers<any>());
});
describe('transform', () => {
@@ -1523,9 +1523,7 @@ describe('change detection', () => {
private resolve?: Function;
changeDetectorRef = inject(ChangeDetectorRef);
createReadPromise() {
this.promise = new Promise<void>((resolve) => {
this.resolve = resolve;
});
({promise: this.promise, resolve: this.resolve} = Promise.withResolvers<void>());
}
resolveReadPromise() {
this.resolve?.();
@@ -88,14 +88,10 @@ describe('public PendingTasks', () => {
const appRef = TestBed.inject(ApplicationRef);
const pendingTasks = TestBed.inject(PendingTasks);
let resolveFn: () => void;
pendingTasks.run(() => {
return new Promise<void>((r) => {
resolveFn = r;
});
});
const {promise, resolve} = Promise.withResolvers<void>();
pendingTasks.run(() => promise);
await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(false);
resolveFn!();
resolve();
await expectAsync(TestBed.inject(ApplicationRef).whenStable()).toBeResolved();
});
@@ -105,14 +101,10 @@ describe('public PendingTasks', () => {
const errorHandler = TestBed.inject(ErrorHandler);
const spy = spyOn(errorHandler, 'handleError');
let rejectFn: () => void;
pendingTasks.run(() => {
return new Promise<void>((_, reject) => {
rejectFn = reject;
});
});
const {promise, reject} = Promise.withResolvers<void>();
pendingTasks.run(() => promise);
await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(false);
rejectFn!();
reject();
await expectAsync(appRef.whenStable()).toBeResolved();
expect(spy).toHaveBeenCalled();
});
+1 -4
View File
@@ -50,10 +50,7 @@ describe('ApplicationInitStatus', () => {
let initFnInvoked = false;
beforeEach(() => {
promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
({promise, resolve, reject} = Promise.withResolvers<any>());
TestBed.configureTestingModule({
providers: [{provide: APP_INITIALIZER, useValue: [() => promise]}],
});
+2 -8
View File
@@ -466,10 +466,7 @@ describe('bootstrap', () => {
}));
it('should wait for asynchronous app initializers', waitForAsync(async () => {
let resolve: (result: any) => void;
const promise: Promise<any> = new Promise((res) => {
resolve = res;
});
const {promise, resolve} = Promise.withResolvers<any>();
let initializerDone = false;
setTimeout(() => {
resolve(true);
@@ -643,10 +640,7 @@ describe('bootstrap', () => {
defaultPlatform = _platform;
}));
it('should wait for asynchronous app initializers', waitForAsync(async () => {
let resolve: (result: any) => void;
const promise: Promise<any> = new Promise((res) => {
resolve = res;
});
const {promise, resolve} = Promise.withResolvers<any>();
let initializerDone = false;
setTimeout(() => {
resolve(true);
@@ -469,10 +469,7 @@ describe('Angular with zoneless enabled', () => {
});
it('executes render hooks when a new one is registered', async () => {
let resolveFn: Function;
let calledPromise = new Promise((resolve) => {
resolveFn = resolve;
});
const {promise: calledPromise, resolve: resolveFn} = Promise.withResolvers<void>();
TestBed.runInInjectionContext(() => {
afterNextRender(() => {
resolveFn();
@@ -496,10 +493,7 @@ describe('Angular with zoneless enabled', () => {
await fixture.whenStable();
expect(checks).toBe(1);
let resolveFn: Function;
let calledPromise = new Promise((resolve) => {
resolveFn = resolve;
});
const {promise: calledPromise, resolve: resolveFn} = Promise.withResolvers<void>();
TestBed.runInInjectionContext(() => {
afterNextRender(() => {
resolveFn();
@@ -62,10 +62,7 @@ describe('injectAsync', () => {
});
await TestBed.runInInjectionContext(async () => {
let prefetchResolve!: () => void;
const prefetchPromise = new Promise<void>((resolve) => {
prefetchResolve = resolve;
});
const {promise: prefetchPromise, resolve: prefetchResolve} = Promise.withResolvers<void>();
let prefetchCalled = false;
const loader = () => {
@@ -2422,13 +2422,11 @@ class PushCmpWithHostEvent {
})
class PushCmpWithAsyncPipe {
numberOfChecks: number = 0;
resolve!: (result: any) => void;
resolve: (result: any) => void;
promise: Promise<any>;
constructor() {
this.promise = new Promise((resolve) => {
this.resolve = resolve;
});
({promise: this.promise, resolve: this.resolve} = Promise.withResolvers<any>());
}
get field() {
@@ -103,17 +103,16 @@ export class MockResourceLoader extends ResourceLoader {
}
class _PendingRequest {
// Using non null assertion, these fields are defined below
// within the `new Promise` callback (synchronously).
resolve!: (result: string) => void;
reject!: (error: any) => void;
resolve: (result: string) => void;
reject: (error: any) => void;
promise: Promise<string>;
constructor(public url: string) {
this.promise = new Promise((res, rej) => {
this.resolve = res;
this.reject = rej;
});
({
promise: this.promise,
resolve: this.resolve,
reject: this.reject,
} = Promise.withResolvers<string>());
}
complete(response: string | null) {
+13 -11
View File
@@ -142,11 +142,12 @@ describe('debounced', () => {
const res = debounced(
source,
() =>
new Promise<void>((resolve) => {
calls++;
release = resolve;
}),
() => {
calls++;
const {promise, resolve} = Promise.withResolvers<void>();
release = resolve;
return promise;
},
{injector},
);
@@ -198,12 +199,13 @@ describe('debounced', () => {
const res = debounced(
source,
(val: string) =>
new Promise<void>((resolve) => {
if (val === 'initial') releaseInitial = resolve;
if (val === 'update1') release1 = resolve;
if (val === 'update2') release2 = resolve;
}),
(val: string) => {
const {promise, resolve} = Promise.withResolvers<void>();
if (val === 'initial') releaseInitial = resolve;
if (val === 'update1') release1 = resolve;
if (val === 'update2') release2 = resolve;
return promise;
},
{injector},
);
+1 -2
View File
@@ -25,7 +25,6 @@ import {
signal,
TransferState,
} from '../../src/core';
import {promiseWithResolvers} from '../../src/util/promise_with_resolvers';
import {TestBed} from '../../testing';
abstract class MockBackend<T, R> {
@@ -344,7 +343,7 @@ describe('resource', () => {
const res = resource({
params: request,
loader: async ({params}) => {
const p = promiseWithResolvers<number>();
const p = Promise.withResolvers<number>();
resolve.push(() => p.resolve(params));
return p.promise;
},
+8 -27
View File
@@ -90,10 +90,7 @@ describe('NgZone', () => {
it('should produce long stack traces', (done) => {
macroTask(() => {
let resolve: (result: any) => void;
const promise: Promise<any> = new Promise((res) => {
resolve = res;
});
const {promise, resolve} = Promise.withResolvers<any>();
_zone.run(() => {
setTimeout(() => {
@@ -114,10 +111,7 @@ describe('NgZone', () => {
it('should produce long stack traces (when using microtasks)', (done) => {
macroTask(() => {
let resolve: (result: any) => void;
const promise: Promise<any> = new Promise((res) => {
resolve = res;
});
const {promise, resolve} = Promise.withResolvers<any>();
_zone.run(() => {
queueMicrotask(() => {
@@ -150,10 +144,7 @@ describe('NgZone', () => {
it('should disable long stack traces', (done) => {
macroTask(() => {
let resolve: (result: any) => void;
const promise: Promise<any> = new Promise((res) => {
resolve = res;
});
const {promise, resolve} = Promise.withResolvers<any>();
_zone.run(() => {
setTimeout(() => {
@@ -524,12 +515,8 @@ function commonTests() {
runNgZoneNoLog(() => {
macroTask(() => {
aPromise = new Promise((res) => {
aResolve = res;
});
bPromise = new Promise((res) => {
bResolve = res;
});
({promise: aPromise, resolve: aResolve} = Promise.withResolvers<string>());
({promise: bPromise, resolve: bResolve} = Promise.withResolvers<string>());
_log.add('run start');
aPromise.then(_log.fn('a then'));
@@ -569,9 +556,7 @@ function commonTests() {
macroTask(() => {
NgZone.assertNotInAngularZone();
promise = new Promise<string | null>((res) => {
resolve = res;
});
({promise, resolve} = Promise.withResolvers<string | null>());
});
runNgZoneNoLog(() => {
@@ -748,12 +733,8 @@ function commonTests() {
runNgZoneNoLog(() => {
macroTask(() => {
aPromise = new Promise<string | null>((res) => {
aResolve = res;
});
bPromise = new Promise<string | null>((res) => {
bResolve = res;
});
({promise: aPromise, resolve: aResolve} = Promise.withResolvers<string | null>());
({promise: bPromise, resolve: bResolve} = Promise.withResolvers<string | null>());
aPromise.then(_log.fn('a then'));
bPromise.then(_log.fn('b then'));
_log.add('run start');
@@ -1570,10 +1570,7 @@ describe('FieldNode', () => {
});
it('should immediately update value on reset even if a debounce is pending', async () => {
let resolveDebounce: (value: void | PromiseLike<void>) => void;
const debouncePromise = new Promise<void>((resolve) => {
resolveDebounce = resolve;
});
const {promise: debouncePromise, resolve: resolveDebounce} = Promise.withResolvers<void>();
const model = signal('initial');
const f = form(
@@ -1598,7 +1595,7 @@ describe('FieldNode', () => {
expect(f().controlValue()).toBe('reset value');
// 3. Resolve the debounce
resolveDebounce!();
resolveDebounce();
await Promise.resolve(); // Wait for promise microtasks
// Value should STILL be 'reset value', not 'user input'
@@ -1635,10 +1632,7 @@ describe('FieldNode', () => {
});
it('should abort pending debounce on reset without value and not trigger further sync', async () => {
let resolveDebounce: (value: void | PromiseLike<void>) => void;
const debouncePromise = new Promise<void>((resolve) => {
resolveDebounce = resolve;
});
const {promise: debouncePromise, resolve: resolveDebounce} = Promise.withResolvers<void>();
const model = signal('initial');
const f = form(
@@ -1664,7 +1658,7 @@ describe('FieldNode', () => {
expect(f().controlValue()).toBe('initial');
// 3. Resolve the debounce
resolveDebounce!();
resolveDebounce();
await Promise.resolve(); // Wait for promise microtasks
// value.set should NEVER have been called during or after reset
@@ -1673,10 +1667,7 @@ describe('FieldNode', () => {
});
it('should abort pending debounce on reset with new value and only call value.set once immediately', async () => {
let resolveDebounce: (value: void | PromiseLike<void>) => void;
const debouncePromise = new Promise<void>((resolve) => {
resolveDebounce = resolve;
});
const {promise: debouncePromise, resolve: resolveDebounce} = Promise.withResolvers<void>();
const model = signal('initial');
const f = form(
@@ -1704,7 +1695,7 @@ describe('FieldNode', () => {
expect(f().value.set).toHaveBeenCalledWith('reset value');
// 3. Resolve the debounce
resolveDebounce!();
resolveDebounce();
await Promise.resolve(); // Wait for promise microtasks
// value.set should STILL have only been called once (the immediate one)
@@ -69,10 +69,7 @@ describe('Signal Forms array removal orphan repro', () => {
}
});
it('should handle debounceSync resolving after the field is orphaned', async () => {
let resolveDebounce!: () => void;
const debouncePromise = new Promise<void>((r) => {
resolveDebounce = r;
});
const {promise: debouncePromise, resolve: resolveDebounce} = Promise.withResolvers<void>();
@Component({
imports: [FormField],
@@ -6300,10 +6300,7 @@ function asyncValidator(
},
) {
return (c: AbstractControl) => {
let resolve: (result: any) => void;
const promise = new Promise<any>((res) => {
resolve = res;
});
const {promise, resolve} = Promise.withResolvers<any>();
const res = checker(c) ? null : error;
setTimeout(() => resolve(res), timeout);
return promise;
@@ -924,12 +924,7 @@ describe('public testing API', () => {
let originalJasmineIt: (description: string, func: () => void) => jasmine.Spec;
const patchJasmineIt = () => {
let resolve: (result: any) => void;
let reject: (error: any) => void;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
const {promise, resolve, reject} = Promise.withResolvers<any>();
const jasmineEnv = jasmine.getEnv() as any;
originalJasmineIt = jasmineEnv.it;
jasmineEnv.it = (description: string, fn: (done: DoneFn) => void): any => {
+2 -10
View File
@@ -90,7 +90,7 @@ describe('bootstrap', () => {
doc.body.appendChild(el1);
doc.body.appendChild(el2);
const {promise, resolveFn} = createPromise();
const {promise, resolve: resolveFn} = Promise.withResolvers<void>();
navigationEndPromise = promise;
log = [];
testProviders = [
@@ -455,7 +455,7 @@ describe('bootstrap', () => {
spyOn(router as any, 'resetRootComponentType').and.callThrough();
const appRef: ApplicationRef = res.injector.get(ApplicationRef);
const {promise, resolveFn} = createPromise();
const {promise, resolve: resolveFn} = Promise.withResolvers<void>();
appRef.components[0].onDestroy(() => {
appRef.bootstrap(SecondRootCmp);
expect((router as any).resetRootComponentType).toHaveBeenCalled();
@@ -629,11 +629,3 @@ function provideNavigationEndAction(fn: Function) {
},
};
}
function createPromise() {
let resolveFn: () => void;
const promise = new Promise<void>((r) => {
resolveFn = r;
});
return {resolveFn: () => resolveFn(), promise};
}
@@ -94,9 +94,9 @@ describe('routerResource behavior tests', () => {
resolveImmediately = false;
return Promise.resolve('initial');
}
return new Promise<string>((resolve) => {
resolveLoader = resolve;
});
const {promise, resolve} = Promise.withResolvers<string>();
resolveLoader = resolve;
return promise;
},
});
}