From 714811c4e637e528d5fda874be40727cdbe9d1a5 Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Tue, 4 Aug 2026 11:46:46 -0400
Subject: [PATCH 1/2] Deprecate RSVP
---
text/1220-deprecate-rsvp.md | 169 ++++++++++++++++++++++++++++++++++++
1 file changed, 169 insertions(+)
create mode 100644 text/1220-deprecate-rsvp.md
diff --git a/text/1220-deprecate-rsvp.md b/text/1220-deprecate-rsvp.md
new file mode 100644
index 0000000000..a1de963b2c
--- /dev/null
+++ b/text/1220-deprecate-rsvp.md
@@ -0,0 +1,169 @@
+---
+stage: accepted
+start-date: 2026-08-04T00:00:00.000Z
+release-date:
+release-versions:
+teams: # delete teams that aren't relevant
+ - framework
+ - learning
+ - typescript
+prs:
+ accepted: https://github.com/emberjs/rfcs/pull/1220
+project-link:
+---
+
+# Deprecate RSVP
+
+## Summary
+
+Deprecate `Ember.RSVP` and the `rsvp` module bundled with `ember-source`.
+
+Native `Promise` has been in every browser and node version we support for a long time now, and covers nearly everything RSVP does.
+[`ember-data` already did this](https://github.com/emberjs/rfcs/pull/796) back in 2022.
+
+The [`rsvp` package on npm](https://www.npmjs.com/package/rsvp) isn't going anywhere -- if folks want to keep using it, they can depend on it directly.
+
+## Motivation
+
+RSVP was created before `Promise` existed in any browser. It was needed then. It is not needed now.
+
+Deprecating it:
+- slims down our public API surface area to more of _what's needed_
+- removes bytes from every app (RSVP is bundled with `ember-source` whether you use it or not)
+- removes one of the remaining ties to the runloop -- `ember-source` configures RSVP to schedule promise resolution via backburner, which is a blocker for eventually removing the runloop
+- removes "another case to cover" for tooling, types, and teaching -- new folks should only ever learn native `Promise`
+
+## Transition Path
+
+Most usage is a mechanical find-and-replace:
+
+| RSVP | Native |
+| ---- | ------ |
+| `RSVP.Promise` / `import { Promise } from 'rsvp'` | `Promise` |
+| `RSVP.resolve(x)` | `Promise.resolve(x)` |
+| `RSVP.reject(x)` | `Promise.reject(x)` |
+| `RSVP.all(array)` | `Promise.all(array)` |
+| `RSVP.race(array)` | `Promise.race(array)` |
+| `RSVP.allSettled(array)` | `Promise.allSettled(array)` [^settled] |
+| `RSVP.defer()` | [`Promise.withResolvers()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers) [^defer] |
+| `RSVP.hash(obj)` | no native equivalent, see below |
+| `RSVP.map(array, fn)` | `Promise.all(array.map(fn))` |
+| `RSVP.filter(array, fn)` | `Promise.all` + `Array.prototype.filter` |
+| `RSVP.denodeify(fn)` | [`util.promisify`](https://nodejs.org/api/util.html#utilpromisifyoriginal) (node), or wrap in `new Promise` |
+| `RSVP.EventTarget` | native [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) |
+| `RSVP.on('error', fn)` | [`unhandledrejection`](https://developer.mozilla.org/en-US/docs/Web/API/Window/unhandledrejection_event) event |
+| `RSVP.rethrow` | not needed, devtools handle async stack traces now |
+
+[^settled]: the result objects differ slightly: RSVP uses `{ state: 'fulfilled' }`, native uses `{ status: 'fulfilled' }`.
+
+[^defer]: `{ promise, resolve, reject }` -- same shape as `RSVP.defer()`.
+
+`RSVP.hash` is the only utility without a native equivalent, and it's a one-liner:
+
+```js
+async function hash(obj) {
+ return Object.fromEntries(
+ await Promise.all(
+ Object.entries(obj).map(async ([key, promise]) => [key, await promise])
+ )
+ );
+}
+```
+
+(or use an existing micro-library, such as the one behind [`WarpDrive`'s `getPromiseState`](https://docs.warp-drive.io), or write it inline -- two `await`s is often clearer than `hash` anyway)
+
+example codemod-ish diff
+
+```diff
+-import RSVP from 'rsvp';
++
+
+ export default class MyService extends Service {
+ async loadEverything() {
+- return RSVP.hash({
+- user: this.store.request(findRecord('user', 1)),
+- settings: fetch('/settings').then((r) => r.json()),
+- });
++ let [user, settings] = await Promise.all([
++ this.store.request(findRecord('user', 1)),
++ fetch('/settings').then((r) => r.json()),
++ ]);
++
++ return { user, settings };
+ }
+ }
+```
+
+
+
+### Timing differences
+
+`ember-source` configures RSVP so that promise resolution is flushed by the runloop. Native promises use the browser's microtask queue directly.
+In practice these are nearly indistinguishable (backburner has been microtask-based since ember-source@3.x), but:
+
+- test code that relied on `await settled()` "seeing" pending RSVP chains should use [`@ember/test-waiters`](https://github.com/emberjs/ember-test-waiters) for any async that renders
+- code that relied on `RSVP.on('error')` for global error reporting should use the `unhandledrejection` event (or `Ember.onerror`, until [that, too, goes away](https://deprecations.emberjs.com/))
+
+### Deprecation mechanics
+
+- accessing `Ember.RSVP` issues a deprecation
+ - `id: deprecate-rsvp`, `until: 7.0.0`
+- importing `'rsvp'` in an app or v1 addon where the module is provided by `ember-source` issues a build-time deprecation
+- apps / addons that install `rsvp` from npm themselves are unaffected -- the deprecation only covers the copy bundled with `ember-source`
+- `ember-source`'s internals (`ember-testing`, router promise handling) migrate to native promises -- not observable except via `instanceof RSVP.Promise` checks, which nobody should be doing
+- a lint rule should be added to `eslint-plugin-ember`'s recommended config flagging `rsvp` imports
+
+### Deprecation guide
+
+> `RSVP` is deprecated. Use native `Promise` instead.
+>
+> Before:
+> ```js
+> import RSVP from 'rsvp';
+>
+> await RSVP.all(promises);
+> ```
+>
+> After:
+> ```js
+> await Promise.all(promises);
+> ```
+>
+> If you need RSVP-specific behavior, add `rsvp` to your own `package.json` -- the npm package is unaffected by this deprecation.
+
+## How We Teach This
+
+The guides and blueprints already use native promises and `async`/`await` everywhere.
+
+Remaining work:
+- add the deprecation guide entry to https://deprecations.emberjs.com
+- mark `Ember.RSVP` / the `rsvp` module as deprecated in the API docs
+
+This is a _reduction_ in what we have to teach: there is no longer a "which Promise?" question.
+
+## Drawbacks
+
+As with any deprecation, we introduce an upgrade cliff for addons that are updated infrequently, and consequently their consuming apps.
+
+The mitigation here is unusually easy though: unlike most deprecations, the replacement (`Promise`) works in _every_ supported Ember version, so addons can migrate today with no `@embroider/macros` dance and no version-range narrowing. Addons that genuinely need RSVP can depend on it from npm directly, which also works across all versions.
+
+The main real cost is timing-sensitive test suites discovering they were implicitly depending on RSVP's runloop scheduling. `@ember/test-waiters` is the answer, and that migration is valuable independent of this RFC.
+
+## Alternatives
+
+do nothing, the cost of bundling RSVP is:
+- bytes in every app, used or not
+- a permanent tie between promise resolution and the runloop
+- mental gymnastics for teaching ("use native promises, except this framework object you may encounter is a different kind of promise")
+- "another case to cover" for tooling and types
+
+deprecate only the runloop integration, keep re-exporting `rsvp`
+- solves the runloop problem, keeps all the other costs
+- at that point `ember-source`'s copy of RSVP has no behavioral difference from the npm package, so re-exporting it serves no purpose
+
+add a lint against `rsvp` imports without deprecating
+- all the downsides of "do nothing" may still be present
+
+## Unresolved questions
+
+n/a
From f9a5ff5f53e559b3ea32c5288603090d15538c5a Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Tue, 4 Aug 2026 12:48:13 -0400
Subject: [PATCH 2/2] Updates
---
text/1220-deprecate-rsvp.md | 37 ++++++++++++++++++-------------------
1 file changed, 18 insertions(+), 19 deletions(-)
diff --git a/text/1220-deprecate-rsvp.md b/text/1220-deprecate-rsvp.md
index a1de963b2c..805bc24fd4 100644
--- a/text/1220-deprecate-rsvp.md
+++ b/text/1220-deprecate-rsvp.md
@@ -16,22 +16,22 @@ project-link:
## Summary
-Deprecate `Ember.RSVP` and the `rsvp` module bundled with `ember-source`.
+Deprecate the `rsvp` module bundled with `ember-source`.
Native `Promise` has been in every browser and node version we support for a long time now, and covers nearly everything RSVP does.
[`ember-data` already did this](https://github.com/emberjs/rfcs/pull/796) back in 2022.
-The [`rsvp` package on npm](https://www.npmjs.com/package/rsvp) isn't going anywhere -- if folks want to keep using it, they can depend on it directly.
+The [`rsvp` package on npm](https://www.npmjs.com/package/rsvp) itself is unaffected by this deprecation, but we recommend migrating to native `Promise` rather than adding a direct dependency on `rsvp`.
## Motivation
-RSVP was created before `Promise` existed in any browser. It was needed then. It is not needed now.
+RSVP is Ember's Promises/A+ implementation from before browsers had one, and native `Promise` has since made almost all of it redundant.
Deprecating it:
- slims down our public API surface area to more of _what's needed_
- removes bytes from every app (RSVP is bundled with `ember-source` whether you use it or not)
-- removes one of the remaining ties to the runloop -- `ember-source` configures RSVP to schedule promise resolution via backburner, which is a blocker for eventually removing the runloop
-- removes "another case to cover" for tooling, types, and teaching -- new folks should only ever learn native `Promise`
+- removes one of the remaining ties to the runloop (`ember-source` configures RSVP to schedule promise resolution via backburner, which blocks eventually removing the runloop)
+- removes "another case to cover" for tooling, types, and teaching. New folks should only ever have to learn native `Promise`
## Transition Path
@@ -56,7 +56,7 @@ Most usage is a mechanical find-and-replace:
[^settled]: the result objects differ slightly: RSVP uses `{ state: 'fulfilled' }`, native uses `{ status: 'fulfilled' }`.
-[^defer]: `{ promise, resolve, reject }` -- same shape as `RSVP.defer()`.
+[^defer]: returns `{ promise, resolve, reject }`, the same shape as `RSVP.defer()`.
`RSVP.hash` is the only utility without a native equivalent, and it's a one-liner:
@@ -70,7 +70,7 @@ async function hash(obj) {
}
```
-(or use an existing micro-library, such as the one behind [`WarpDrive`'s `getPromiseState`](https://docs.warp-drive.io), or write it inline -- two `await`s is often clearer than `hash` anyway)
+(or write it inline; two `await`s is often clearer than `hash` anyway)
example codemod-ish diff
@@ -102,15 +102,14 @@ async function hash(obj) {
In practice these are nearly indistinguishable (backburner has been microtask-based since ember-source@3.x), but:
- test code that relied on `await settled()` "seeing" pending RSVP chains should use [`@ember/test-waiters`](https://github.com/emberjs/ember-test-waiters) for any async that renders
-- code that relied on `RSVP.on('error')` for global error reporting should use the `unhandledrejection` event (or `Ember.onerror`, until [that, too, goes away](https://deprecations.emberjs.com/))
+- code that relied on `RSVP.on('error')` for global error reporting should use the `unhandledrejection` event
### Deprecation mechanics
-- accessing `Ember.RSVP` issues a deprecation
- - `id: deprecate-rsvp`, `until: 7.0.0`
-- importing `'rsvp'` in an app or v1 addon where the module is provided by `ember-source` issues a build-time deprecation
-- apps / addons that install `rsvp` from npm themselves are unaffected -- the deprecation only covers the copy bundled with `ember-source`
-- `ember-source`'s internals (`ember-testing`, router promise handling) migrate to native promises -- not observable except via `instanceof RSVP.Promise` checks, which nobody should be doing
+- the `rsvp` module provided by `ember-source` issues a runtime deprecation (via the existing deprecation system, `deprecate` from `@ember/debug`) when any of its exports are used
+ - `id: deprecate-rsvp`, `until: 8.0.0`
+- the deprecation only covers the copy bundled with `ember-source`. Installing `rsvp` from npm directly would silence it, but migrating to native `Promise` is the recommended path
+- `ember-source`'s internals (`ember-testing`, router promise handling) migrate to native promises. This is not observable, except via `instanceof RSVP.Promise` checks, which nobody should be doing
- a lint rule should be added to `eslint-plugin-ember`'s recommended config flagging `rsvp` imports
### Deprecation guide
@@ -129,7 +128,7 @@ In practice these are nearly indistinguishable (backburner has been microtask-ba
> await Promise.all(promises);
> ```
>
-> If you need RSVP-specific behavior, add `rsvp` to your own `package.json` -- the npm package is unaffected by this deprecation.
+> We recommend migrating to native `Promise` rather than adding a dependency on the `rsvp` npm package; everything RSVP provides has a native equivalent or a small inline replacement.
## How We Teach This
@@ -137,24 +136,24 @@ The guides and blueprints already use native promises and `async`/`await` everyw
Remaining work:
- add the deprecation guide entry to https://deprecations.emberjs.com
-- mark `Ember.RSVP` / the `rsvp` module as deprecated in the API docs
+- mark the `rsvp` module as deprecated in the API docs
-This is a _reduction_ in what we have to teach: there is no longer a "which Promise?" question.
+Overall, this reduces what we have to teach, since there is only one kind of promise left.
## Drawbacks
As with any deprecation, we introduce an upgrade cliff for addons that are updated infrequently, and consequently their consuming apps.
-The mitigation here is unusually easy though: unlike most deprecations, the replacement (`Promise`) works in _every_ supported Ember version, so addons can migrate today with no `@embroider/macros` dance and no version-range narrowing. Addons that genuinely need RSVP can depend on it from npm directly, which also works across all versions.
+Unlike most deprecations, though, the replacement (`Promise`) works in every supported Ember version, so addons can migrate today without `@embroider/macros` and without narrowing their supported version range.
-The main real cost is timing-sensitive test suites discovering they were implicitly depending on RSVP's runloop scheduling. `@ember/test-waiters` is the answer, and that migration is valuable independent of this RFC.
+The bigger cost is timing-sensitive test suites that implicitly depend on RSVP's runloop scheduling. Those need `@ember/test-waiters`, which they should be using regardless of this RFC.
## Alternatives
do nothing, the cost of bundling RSVP is:
- bytes in every app, used or not
- a permanent tie between promise resolution and the runloop
-- mental gymnastics for teaching ("use native promises, except this framework object you may encounter is a different kind of promise")
+- mental gymnastics for teaching ("use native promises, except this module the framework ships is a different kind of promise")
- "another case to cover" for tooling and types
deprecate only the runloop integration, keep re-exporting `rsvp`