From f6b185e7b12958aa44e33593cecce52a1678050d Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 5 May 2026 13:19:40 -0400 Subject: [PATCH 1/3] Remove RSVP from internal code Better types Better types --- .../@ember/-internals/glimmer/lib/renderer.ts | 19 ++++++++------ packages/@ember/application/index.ts | 15 +++++++---- packages/@ember/engine/instance.ts | 5 ++-- packages/@ember/object/promise-proxy-mixin.ts | 25 ++++++++----------- packages/@ember/routing/route.ts | 3 +-- .../internal-test-helpers/lib/module-for.ts | 3 +-- packages/internal-test-helpers/lib/run.ts | 2 -- packages/router_js/lib/route-info.ts | 9 +++---- packages/router_js/lib/router.ts | 11 +++----- packages/router_js/lib/transition-state.ts | 11 ++++---- packages/router_js/lib/transition.ts | 18 ++++++------- packages/router_js/lib/utils.ts | 1 - .../router_js/tests/async_get_handler_test.ts | 1 - packages/router_js/tests/query_params_test.ts | 1 - packages/router_js/tests/route_info_test.ts | 7 +++--- packages/router_js/tests/router_test.ts | 11 ++++---- .../router_js/tests/transition_intent_test.ts | 1 - .../router_js/tests/transition_state_test.ts | 9 +++---- 18 files changed, 68 insertions(+), 84 deletions(-) diff --git a/packages/@ember/-internals/glimmer/lib/renderer.ts b/packages/@ember/-internals/glimmer/lib/renderer.ts index cfc74e0e423..06b5d68c264 100644 --- a/packages/@ember/-internals/glimmer/lib/renderer.ts +++ b/packages/@ember/-internals/glimmer/lib/renderer.ts @@ -48,7 +48,6 @@ import { dict } from '@glimmer/util'; import { unwrapTemplate } from './component-managers/unwrap-template'; import { CURRENT_TAG, validateTag, valueForTag } from '@glimmer/validator'; import type { SimpleDocument, SimpleElement, SimpleNode } from '@simple-dom/interface'; -import RSVP from 'rsvp'; import type Component from './component'; import { hasDOM } from '../../browser-environment'; import type ClassicComponent from './component'; @@ -297,7 +296,8 @@ function loopBegin(): void { } } -let renderSettledDeferred: RSVP.Deferred | null = null; +let renderSettledPromise: Promise | null = null; +let renderSettledResolve: (() => void) | null = null; /* Returns a promise which will resolve when rendering has settled. Settled in this context is defined as when all of the tags in use are "current" (e.g. @@ -308,8 +308,10 @@ let renderSettledDeferred: RSVP.Deferred | null = null; @returns {Promise} a promise which fulfills when rendering has settled */ export function renderSettled() { - if (renderSettledDeferred === null) { - renderSettledDeferred = RSVP.defer(); + if (renderSettledPromise === null) { + renderSettledPromise = new Promise((resolve) => { + renderSettledResolve = resolve; + }); // if there is no current runloop, the promise created above will not have // a chance to resolve (because its resolved in backburner's "end" event) if (!_getCurrentRunLoop()) { @@ -318,13 +320,14 @@ export function renderSettled() { } } - return renderSettledDeferred.promise; + return renderSettledPromise; } function resolveRenderPromise() { - if (renderSettledDeferred !== null) { - let resolve = renderSettledDeferred.resolve; - renderSettledDeferred = null; + if (renderSettledResolve !== null) { + let resolve = renderSettledResolve; + renderSettledPromise = null; + renderSettledResolve = null; _backburner.join(null, resolve); } diff --git a/packages/@ember/application/index.ts b/packages/@ember/application/index.ts index dc6d923835a..efeb527c18a 100644 --- a/packages/@ember/application/index.ts +++ b/packages/@ember/application/index.ts @@ -10,7 +10,6 @@ import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { join, once, run, schedule } from '@ember/runloop'; import { libraries } from '@ember/-internals/metal'; -import { RSVP } from '@ember/-internals/runtime'; import { EventDispatcher } from '@ember/-internals/views'; import Route from '@ember/routing/route'; import Router from '@ember/routing/router'; @@ -681,7 +680,10 @@ class Application extends Engine { return this._bootPromise; } - _bootResolver: ReturnType<(typeof RSVP)['defer']> | null = null; + _bootResolver: { + resolve: (value: Application) => void; + reject: (reason?: unknown) => void; + } | null = null; /** Unfortunately, a lot of existing code assumes the booting process is @@ -705,8 +707,11 @@ class Application extends Engine { // boot promise exists for book-keeping purposes: if anything went wrong in // the boot process, we need to store the error as a rejection on the boot // promise so that a future caller of `boot()` can tell what failed. - let defer = (this._bootResolver = RSVP.defer()); - this._bootPromise = defer.promise as Promise; + let resolver!: { resolve: (value: Application) => void; reject: (reason?: unknown) => void }; + this._bootPromise = new Promise((resolve, reject) => { + resolver = { resolve: resolve as (value: Application) => void, reject }; + }); + this._bootResolver = resolver; try { this.runInitializers(); @@ -714,7 +719,7 @@ class Application extends Engine { // Continues to `didBecomeReady` } catch (error) { // For the asynchronous boot path - defer.reject(error); + resolver.reject(error); // For the synchronous boot path throw error; diff --git a/packages/@ember/engine/instance.ts b/packages/@ember/engine/instance.ts index 097b5c6ca6e..eff92b8217f 100644 --- a/packages/@ember/engine/instance.ts +++ b/packages/@ember/engine/instance.ts @@ -3,7 +3,6 @@ */ import EmberObject from '@ember/object'; -import { RSVP } from '@ember/-internals/runtime'; import { assert } from '@ember/debug'; import { Registry, privatize as P } from '@ember/-internals/container'; import { guidFor } from '@ember/-internals/utils'; @@ -101,7 +100,7 @@ class EngineInstance extends EmberObject.extend(RegistryProxyMixin, ContainerPro this._booted = false; } - _bootPromise: RSVP.Promise | null = null; + _bootPromise: Promise | null = null; /** Initialize the `EngineInstance` and return a promise that resolves @@ -121,7 +120,7 @@ class EngineInstance extends EmberObject.extend(RegistryProxyMixin, ContainerPro return this._bootPromise; } - this._bootPromise = new RSVP.Promise((resolve) => { + this._bootPromise = new Promise((resolve) => { resolve(this._bootSync(options)); }); diff --git a/packages/@ember/object/promise-proxy-mixin.ts b/packages/@ember/object/promise-proxy-mixin.ts index eafe2a9522d..d92fcebdc7e 100644 --- a/packages/@ember/object/promise-proxy-mixin.ts +++ b/packages/@ember/object/promise-proxy-mixin.ts @@ -1,14 +1,13 @@ import { get, setProperties, computed } from '@ember/object'; import Mixin from '@ember/object/mixin'; import type { AnyFn, MethodNamesOf } from '@ember/-internals/utility-types'; -import type RSVP from 'rsvp'; import type CoreObject from '@ember/object/core'; /** @module @ember/object/promise-proxy-mixin */ -function tap(proxy: PromiseProxyMixin, promise: RSVP.Promise) { +function tap(proxy: PromiseProxyMixin, promise: Promise) { setProperties(proxy, { isFulfilled: false, isRejected: false, @@ -38,8 +37,7 @@ function tap(proxy: PromiseProxyMixin, promise: RSVP.Promise) { }); } throw reason; - }, - 'Ember: PromiseProxy' + } ); } @@ -47,7 +45,6 @@ function tap(proxy: PromiseProxyMixin, promise: RSVP.Promise) { A low level mixin making ObjectProxy promise-aware. ```javascript - import { resolve } from 'rsvp'; import $ from 'jquery'; import ObjectProxy from '@ember/object/proxy'; import PromiseProxyMixin from '@ember/object/promise-proxy-mixin'; @@ -55,7 +52,7 @@ function tap(proxy: PromiseProxyMixin, promise: RSVP.Promise) { let ObjectPromiseProxy = ObjectProxy.extend(PromiseProxyMixin); let proxy = ObjectPromiseProxy.create({ - promise: resolve($.getJSON('/some/remote/data.json')) + promise: Promise.resolve($.getJSON('/some/remote/data.json')) }); proxy.then(function(json){ @@ -78,7 +75,7 @@ function tap(proxy: PromiseProxyMixin, promise: RSVP.Promise) { When the $.getJSON completes, and the promise is fulfilled with json, the life cycle attributes will update accordingly. Note that $.getJSON doesn't return an ECMA specified promise, - it is useful to wrap this with an `RSVP.resolve` so that it behaves + it is useful to wrap this with `Promise.resolve` so that it behaves as a spec compliant promise. ```javascript @@ -176,22 +173,22 @@ interface PromiseProxyMixin { /** An alias to the proxied promise's `then`. - See RSVP.Promise.then. + See Promise.prototype.then. @method then @param {Function} callback - @return {RSVP.Promise} + @return {Promise} @public */ then: this['promise']['then']; /** An alias to the proxied promise's `catch`. - See RSVP.Promise.catch. + See Promise.prototype.catch. @method catch @param {Function} callback - @return {RSVP.Promise} + @return {Promise} @since 1.3.0 @public */ @@ -199,11 +196,11 @@ interface PromiseProxyMixin { /** An alias to the proxied promise's `finally`. - See RSVP.Promise.finally. + See Promise.prototype.finally. @method finally @param {Function} callback - @return {RSVP.Promise} + @return {Promise} @since 1.3.0 @public */ @@ -228,7 +225,7 @@ const PromiseProxyMixin = Mixin.create({ get() { throw new Error("PromiseProxy's promise must be set"); }, - set(_key, promise: RSVP.Promise) { + set(_key, promise: Promise) { return tap(this, promise); }, }), diff --git a/packages/@ember/routing/route.ts b/packages/@ember/routing/route.ts index 7b4a8f24eac..a7b38ee7321 100644 --- a/packages/@ember/routing/route.ts +++ b/packages/@ember/routing/route.ts @@ -198,7 +198,6 @@ interface Route extends IRoute, ActionHandler, Evented { as well as any unhandled errors from child routes: ```app/routes/admin.js - import { reject } from 'rsvp'; import Route from '@ember/routing/route'; import { action } from '@ember/object'; import { service } from '@ember/service'; @@ -207,7 +206,7 @@ interface Route extends IRoute, ActionHandler, Evented { @service router; beforeModel() { - return reject('bad things!'); + return Promise.reject('bad things!'); } @action diff --git a/packages/internal-test-helpers/lib/module-for.ts b/packages/internal-test-helpers/lib/module-for.ts index b091ef50b98..4429d5a738f 100644 --- a/packages/internal-test-helpers/lib/module-for.ts +++ b/packages/internal-test-helpers/lib/module-for.ts @@ -2,7 +2,6 @@ import { isEnabled } from '@ember/canary-features'; import { assertDestroyablesDestroyed, enableDestroyableTracking } from '@glimmer/destroyable'; import { DEBUG } from '@glimmer/env'; -import { all } from 'rsvp'; import type { Generator, Mixin } from './apply-mixins'; import applyMixins from './apply-mixins'; import getAllPropertyNames from './get-all-property-names'; @@ -122,7 +121,7 @@ export function setupTestClass( // promise when it is not needed let filteredPromises = promises.filter(Boolean); if (filteredPromises.length > 0) { - return all(filteredPromises) + return Promise.all(filteredPromises) .finally(afterEachFinally) .then(() => {}); } diff --git a/packages/internal-test-helpers/lib/run.ts b/packages/internal-test-helpers/lib/run.ts index f7465737645..f759941a2be 100644 --- a/packages/internal-test-helpers/lib/run.ts +++ b/packages/internal-test-helpers/lib/run.ts @@ -1,8 +1,6 @@ import { next, run, _getCurrentRunLoop, _hasScheduledTimers } from '@ember/runloop'; import { destroy } from '@glimmer/destroyable'; -import { Promise } from 'rsvp'; - export function runAppend(view: any): void { run(view, 'appendTo', document.getElementById('qunit-fixture')); } diff --git a/packages/router_js/lib/route-info.ts b/packages/router_js/lib/route-info.ts index ecd4875b1a8..a177ba19baa 100644 --- a/packages/router_js/lib/route-info.ts +++ b/packages/router_js/lib/route-info.ts @@ -1,5 +1,4 @@ /* eslint-disable no-prototype-builtins */ -import { Promise } from 'rsvp'; import type { Dict, Option } from './core'; import type { SerializerFunc } from './router'; import type Router from './router'; @@ -234,8 +233,8 @@ export default class InternalRouteInfo { } } - getModel(_transition: InternalTransition) { - return Promise.resolve(this.context); + getModel(_transition: InternalTransition): Promise | undefined> { + return Promise.resolve | undefined>(this.context); } serialize(_context?: ModelFor | null): Dict | undefined { @@ -477,7 +476,7 @@ export class UnresolvedRouteInfoByParam extends InternalRouteIn } } - getModel(transition: InternalTransition): Promise> { + getModel(transition: InternalTransition): Promise | undefined> { let fullParams = this.params; if (transition && transition[QUERY_PARAMS_SYMBOL]) { fullParams = {}; @@ -506,7 +505,7 @@ export class UnresolvedRouteInfoByParam extends InternalRouteIn result = undefined; } - return Promise.resolve(result); + return Promise.resolve | undefined>(result); } } diff --git a/packages/router_js/lib/router.ts b/packages/router_js/lib/router.ts index 0dd1300ae8c..07b8c6dfbc9 100644 --- a/packages/router_js/lib/router.ts +++ b/packages/router_js/lib/router.ts @@ -1,7 +1,6 @@ /* eslint-disable no-prototype-builtins */ import type { MatchCallback, Params, QueryParams } from 'route-recognizer'; import RouteRecognizer from 'route-recognizer'; -import { Promise } from 'rsvp'; import type { Dict, Maybe, Option } from './core'; import type { ModelFor, Route, RouteInfo, RouteInfoWithAttributes } from './route-info'; import type InternalRouteInfo from './route-info'; @@ -15,7 +14,7 @@ import URLTransitionIntent from './transition-intent/url-transition-intent'; import type { TransitionError } from './transition-state'; import TransitionState from './transition-state'; import type { ChangeList, ModelsAndQueryParams } from './utils'; -import { extractQueryParams, forEach, getChangelist, log, merge, promiseLabel } from './utils'; +import { extractQueryParams, forEach, getChangelist, log, merge } from './utils'; export interface SerializerFunc { (model: T, params: string[]): Dict; @@ -132,9 +131,7 @@ export default abstract class Router { this.routeDidChange(newTransition); } return result; - }, - null, - promiseLabel('Transition complete') + } ); return newTransition; @@ -261,9 +258,7 @@ export default abstract class Router { newTransition.promise = newTransition.promise!.then( (result: TransitionState) => { return this.finalizeTransition(newTransition, result); - }, - null, - promiseLabel('Settle transition promise when transition is finalized') + } ); if (!wasTransitioning) { diff --git a/packages/router_js/lib/transition-state.ts b/packages/router_js/lib/transition-state.ts index e10c7c7361c..1e09858805e 100644 --- a/packages/router_js/lib/transition-state.ts +++ b/packages/router_js/lib/transition-state.ts @@ -1,4 +1,3 @@ -import { Promise } from 'rsvp'; import type { Dict } from './core'; import type { Route, ResolvedRouteInfo } from './route-info'; import type InternalRouteInfo from './route-info'; @@ -47,7 +46,7 @@ function resolveOneRouteInfo( resolvedRouteInfo: ResolvedRouteInfo ) => void | Promise; - return routeInfo.resolve(transition).then(callback, null, currentState.promiseLabel('Proceed')); + return routeInfo.resolve(transition).then(callback); } function proceed( @@ -112,10 +111,10 @@ export default class TransitionState { let callback = resolveOneRouteInfo.bind(null, this, transition); let errorHandler = handleError.bind(null, this, transition); - // The prelude RSVP.resolve() async moves us into the promise land. - return Promise.resolve(null, this.promiseLabel('Start transition')) - .then(callback, null, this.promiseLabel('Resolve route')) - .catch(errorHandler, this.promiseLabel('Handle error')) + // The prelude Promise.resolve() async moves us into the promise land. + return Promise.resolve(null) + .then(callback) + .catch(errorHandler) .then(() => this); } } diff --git a/packages/router_js/lib/transition.ts b/packages/router_js/lib/transition.ts index b4c0e26634c..5da31d6f752 100644 --- a/packages/router_js/lib/transition.ts +++ b/packages/router_js/lib/transition.ts @@ -1,4 +1,3 @@ -import { Promise } from 'rsvp'; import type { Dict, Maybe, Option } from './core'; import type { ModelFor, Route, RouteInfo, RouteInfoWithAttributes } from './route-info'; import type InternalRouteInfo from './route-info'; @@ -8,7 +7,7 @@ import { buildTransitionAborted } from './transition-aborted-error'; import type { OpaqueIntent } from './transition-intent'; import type { TransitionError } from './transition-state'; import type TransitionState from './transition-state'; -import { log, promiseLabel } from './utils'; +import { log } from './utils'; import { DEBUG } from '@glimmer/env'; export type OnFulfilled = @@ -179,7 +178,7 @@ export default class Transition implements Partial> let error = this.router.transitionDidError(result, this); throw error; - }, promiseLabel('Handle Abort')); + }); } else { this.promise = Promise.resolve(this[STATE_SYMBOL]!); this[PARAMS_SYMBOL] = {}; @@ -230,9 +229,9 @@ export default class Transition implements Partial> then( onFulfilled?: ((value: R) => TResult1 | PromiseLike) | undefined | null, onRejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, - label?: string + _label?: string ): Promise { - return this.promise!.then(onFulfilled, onRejected, label); + return this.promise!.then(onFulfilled, onRejected); } /** @@ -248,8 +247,8 @@ export default class Transition implements Partial> @return {Promise} @public */ - catch(onRejection?: OnRejected, T>, label?: string) { - return this.promise!.catch(onRejection, label); + catch(onRejection?: OnRejected, T>, _label?: string) { + return this.promise!.catch(onRejection); } /** @@ -265,9 +264,8 @@ export default class Transition implements Partial> @return {Promise} @public */ - finally(callback?: T | undefined, label?: string) { - // @ts-expect-error @types/rsvp doesn't have the correct signiture for RSVP.Promise.finally - return this.promise!.finally(callback, label); + finally(callback?: T | undefined, _label?: string) { + return this.promise!.finally(callback as never); } /** diff --git a/packages/router_js/lib/utils.ts b/packages/router_js/lib/utils.ts index 38912f6c240..609ec6ca21e 100644 --- a/packages/router_js/lib/utils.ts +++ b/packages/router_js/lib/utils.ts @@ -1,5 +1,4 @@ import type { QueryParams } from 'route-recognizer'; -import type { Promise } from 'rsvp'; import type { Dict } from './core'; import type Router from './router'; diff --git a/packages/router_js/tests/async_get_handler_test.ts b/packages/router_js/tests/async_get_handler_test.ts index 73637c8610a..7d780e50c16 100644 --- a/packages/router_js/tests/async_get_handler_test.ts +++ b/packages/router_js/tests/async_get_handler_test.ts @@ -1,6 +1,5 @@ import type { Route } from '../index'; import type { Dict } from '../lib/core'; -import { Promise } from 'rsvp'; import { createHandler, TestRouter } from './test_helpers'; function map(router: TestRouter) { diff --git a/packages/router_js/tests/query_params_test.ts b/packages/router_js/tests/query_params_test.ts index d816289fa9a..0494089cdd2 100644 --- a/packages/router_js/tests/query_params_test.ts +++ b/packages/router_js/tests/query_params_test.ts @@ -4,7 +4,6 @@ import type { Route, Transition } from '../index'; import type Router from '../index'; import type { Dict, Maybe } from '../lib/core'; import type RouteInfo from '../lib/route-info'; -import { Promise } from 'rsvp'; import { createHandler, TestRouter, trigger, ignoreTransitionError } from './test_helpers'; let router: Router, handlers: Dict, expectedUrl: Maybe; diff --git a/packages/router_js/tests/route_info_test.ts b/packages/router_js/tests/route_info_test.ts index 31ba500e7b7..aed26d7b9ed 100644 --- a/packages/router_js/tests/route_info_test.ts +++ b/packages/router_js/tests/route_info_test.ts @@ -9,7 +9,6 @@ import { } from '../lib/route-info'; import InternalTransition from '../lib/transition'; import URLTransitionIntent from '../lib/transition-intent/url-transition-intent'; -import { resolve } from 'rsvp'; import { createHandler, createHandlerInfo, TestRouter } from './test_helpers'; QUnit.module('RouteInfo'); @@ -122,11 +121,11 @@ QUnit.skip('RouteInfo#resolve runs afterModel hook on handler', function (assert afterModel(resolvedModel: Dict, payload: Dict) { assert.equal(resolvedModel, model, 'afterModel receives the value resolved by model'); assert.equal(payload, transition); - return resolve(123); // 123 should get ignored + return Promise.resolve(123); // 123 should get ignored }, }), getModel() { - return resolve(model); + return Promise.resolve(model); }, }); @@ -186,7 +185,7 @@ QUnit.test('UnresolvedRouteInfoByObject does NOT get its model hook called', fun new TestRouter(), 'unresolved', ['wat'], - resolve({ name: 'dorkletons' }) + Promise.resolve({ name: 'dorkletons' }) ); routeInfo.resolve({} as Transition).then((resolvedRouteInfo) => { diff --git a/packages/router_js/tests/router_test.ts b/packages/router_js/tests/router_test.ts index 2f8d320a751..39edc406e6e 100644 --- a/packages/router_js/tests/router_test.ts +++ b/packages/router_js/tests/router_test.ts @@ -12,7 +12,6 @@ import type RouteInfo from '../lib/route-info'; import type { SerializerFunc } from '../lib/router'; import { logAbort, PARAMS_SYMBOL, QUERY_PARAMS_SYMBOL, STATE_SYMBOL } from '../lib/transition'; import type { TransitionError } from '../lib/transition-state'; -import { Promise, reject } from 'rsvp'; import { assertAbort, createHandler, @@ -3320,7 +3319,7 @@ scenarios.forEach(function (scenario) { function redirectToAbout() { if (returnPromise) { - return reject().then(null, function () { + return Promise.reject().then(null, function () { router.transitionTo(redirectTo); }); } else { @@ -3427,7 +3426,7 @@ scenarios.forEach(function (scenario) { let expectedReason = { reason: 'No funciona, mon frere.' }; function throwAnError() { - return reject(expectedReason); + return Promise.reject(expectedReason); } routes = { @@ -3611,7 +3610,7 @@ scenarios.forEach(function (scenario) { showPost: createHandler('showPost', { model: function () { - return reject('borf!'); + return Promise.reject('borf!'); }, events: { error: function (e: Error) { @@ -3624,7 +3623,7 @@ scenarios.forEach(function (scenario) { if (errorCount === 1) { // transition back here to test transitionTo error handling. return router - .transitionTo('showPost', reject('borf!')) + .transitionTo('showPost', Promise.reject('borf!')) .then(shouldNotHappen(assert), function (e: Error) { assert.equal(e, 'borf!', 'got thing'); }); @@ -5686,7 +5685,7 @@ scenarios.forEach(function (scenario) { foo: createHandler('foo', { model: function () { router.intermediateTransitionTo('loading'); - return new Promise(function (resolve) { + return new Promise(function (resolve) { resolve(); }); }, diff --git a/packages/router_js/tests/transition_intent_test.ts b/packages/router_js/tests/transition_intent_test.ts index 028c1e70001..6af05c8e519 100644 --- a/packages/router_js/tests/transition_intent_test.ts +++ b/packages/router_js/tests/transition_intent_test.ts @@ -11,7 +11,6 @@ import { UnresolvedRouteInfoByObject, UnresolvedRouteInfoByParam, } from '../lib/route-info'; -import { Promise } from 'rsvp'; let handlers: Dict, recognizer: any; diff --git a/packages/router_js/tests/transition_state_test.ts b/packages/router_js/tests/transition_state_test.ts index 099d3e0f40a..3772b3dfced 100644 --- a/packages/router_js/tests/transition_state_test.ts +++ b/packages/router_js/tests/transition_state_test.ts @@ -6,7 +6,6 @@ import { UnresolvedRouteInfoByParam, } from '../lib/route-info'; import TransitionState, { type TransitionError } from '../lib/transition-state'; -import { Promise, resolve } from 'rsvp'; import { createHandler, createHandlerInfo, TestRouter } from './test_helpers'; QUnit.module('TransitionState'); @@ -30,14 +29,14 @@ QUnit.test("#resolve delegates to handleInfo objects' resolve()", function (asse resolve: function () { ++counter; assert.equal(counter, 1); - return resolve(resolvedHandlerInfos[0]); + return Promise.resolve(resolvedHandlerInfos[0]); }, }), createHandlerInfo('two', { resolve: function () { ++counter; assert.equal(counter, 2); - return resolve(resolvedHandlerInfos[1]); + return Promise.resolve(resolvedHandlerInfos[1]); }, }), ]; @@ -90,11 +89,11 @@ QUnit.test('Integration w/ HandlerInfos', function (assert) { model: function (params: Dict, payload: Dict) { assert.equal(payload, transition); assert.equal(params['foo_id'], '123', 'foo#model received expected params'); - return resolve(fooModel); + return Promise.resolve(fooModel); }, }) ), - new UnresolvedRouteInfoByObject(router, 'bar', ['bar_id'], resolve(barModel)), + new UnresolvedRouteInfoByObject(router, 'bar', ['bar_id'], Promise.resolve(barModel)), ]; state From 47ce228c6e55bfa34840bead3e7c614d13a4c03c Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 5 May 2026 13:57:34 -0400 Subject: [PATCH 2/3] Prettier --- packages/router_js/lib/router.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/router_js/lib/router.ts b/packages/router_js/lib/router.ts index 07b8c6dfbc9..5bcb2d69bd8 100644 --- a/packages/router_js/lib/router.ts +++ b/packages/router_js/lib/router.ts @@ -255,11 +255,9 @@ export default abstract class Router { // Transition promises by default resolve with resolved state. // For our purposes, swap out the promise to resolve // after the transition has been finalized. - newTransition.promise = newTransition.promise!.then( - (result: TransitionState) => { - return this.finalizeTransition(newTransition, result); - } - ); + newTransition.promise = newTransition.promise!.then((result: TransitionState) => { + return this.finalizeTransition(newTransition, result); + }); if (!wasTransitioning) { this.notifyExistingHandlers(newState, newTransition); From b0414862e1489bb862d28046f437b430375a6ce2 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 5 May 2026 14:49:07 -0400 Subject: [PATCH 3/3] Add type tests for RSVP<->Promise relationship to prove no breaking change --- .../@ember/routing-test/router-service.ts | 11 ++++--- type-tests/@ember/rsvp-compat-tests.ts | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 type-tests/@ember/rsvp-compat-tests.ts diff --git a/type-tests/@ember/routing-test/router-service.ts b/type-tests/@ember/routing-test/router-service.ts index 2b6083b8a34..8615f2af818 100644 --- a/type-tests/@ember/routing-test/router-service.ts +++ b/type-tests/@ember/routing-test/router-service.ts @@ -3,7 +3,6 @@ import type RouteInfo from '@ember/routing/route-info'; import RouterService from '@ember/routing/router-service'; import type Transition from '@ember/routing/transition'; import { expectTypeOf } from 'expect-type'; -import type { Promise as RSVPPromise } from 'rsvp'; declare let router: RouterService; @@ -28,21 +27,21 @@ const transition = router.transitionTo('someRoute'); expectTypeOf(transition.abort()).toEqualTypeOf(); -expectTypeOf(transition.catch()).toEqualTypeOf>(); +expectTypeOf(transition.catch()).toEqualTypeOf>(); transition.catch((err) => console.log(err), 'label'); -expectTypeOf(transition.finally()).toEqualTypeOf>(); +expectTypeOf(transition.finally()).toEqualTypeOf>(); transition.finally(() => console.log('finally!')); transition.finally(() => console.log('finally!'), 'label'); -expectTypeOf(transition.followRedirects()).toEqualTypeOf>(); +expectTypeOf(transition.followRedirects()).toEqualTypeOf>(); expectTypeOf(transition.method('refresh')).toEqualTypeOf(); transition.method('replace'); expectTypeOf(transition.retry()).toEqualTypeOf(); -expectTypeOf(transition.then()).toEqualTypeOf>(); +expectTypeOf(transition.then()).toEqualTypeOf>(); transition.then( (result) => console.log(result), (err) => console.log(err), @@ -59,7 +58,7 @@ expectTypeOf(transition.from).toEqualTypeOf | undefined>(); +expectTypeOf(transition.promise).toEqualTypeOf | undefined>(); // @ts-expect-error transition.promise = 'promise'; diff --git a/type-tests/@ember/rsvp-compat-tests.ts b/type-tests/@ember/rsvp-compat-tests.ts new file mode 100644 index 00000000000..6b754d201ec --- /dev/null +++ b/type-tests/@ember/rsvp-compat-tests.ts @@ -0,0 +1,30 @@ +import { expectTypeOf } from 'expect-type'; + +/** + * We no longer use RSVP internally, + * but to ensure we don't accidentally break compatibility + * with users who are still using it, I wanted verification + * that apis RSVP promises satisfy native promise types. + */ +import RSVP from 'rsvp'; + +const nativePromise = new Promise((resolve) => resolve(0)); + +expectTypeOf(nativePromise).not.toMatchTypeOf>(); +expectTypeOf(nativePromise).toMatchTypeOf>(); + +const rsvpPromise = new RSVP.Promise((resolve) => resolve(0)); + +expectTypeOf(rsvpPromise).toMatchTypeOf>(); +expectTypeOf(rsvpPromise).toMatchTypeOf>(); + +function takesNativePromise(p: Promise) {} + +function takesRSVPPromise(p: RSVP.Promise) {} + +takesNativePromise(nativePromise); +takesNativePromise(rsvpPromise); + +takesRSVPPromise(rsvpPromise); +// @ts-expect-error RSVP.Promise is not a native Promise +takesRSVPPromise(nativePromise); \ No newline at end of file