-
-
Notifications
You must be signed in to change notification settings - Fork 408
Deprecate RSVP #1220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Deprecate RSVP #1220
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| --- | ||
| 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 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) itself is unaffected by this deprecation, but we recommend migrating to native `Promise` rather than adding a direct dependency on `rsvp`. | ||
|
|
||
| ## Motivation | ||
|
|
||
| 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 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 | ||
|
|
||
| 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` | | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one get's a little bit complicated. I think this will have to be
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also just note that people can re-install RSVP and use it. The main thing we want to do is stop having it built-in to ember / stop overriding its flush in ember |
||
| | `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]: 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: | ||
|
|
||
| ```js | ||
| async function hash(obj) { | ||
| return Object.fromEntries( | ||
| await Promise.all( | ||
| Object.entries(obj).map(async ([key, promise]) => [key, await promise]) | ||
| ) | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| (or write it inline; two `await`s is often clearer than `hash` anyway) | ||
|
|
||
| <details><summary>example codemod-ish diff</summary> | ||
|
|
||
| ```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 }; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| </details> | ||
|
|
||
| ### 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 | ||
|
|
||
| ### Deprecation mechanics | ||
|
|
||
| - 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 | ||
|
|
||
| > `RSVP` is deprecated. Use native `Promise` instead. | ||
| > | ||
| > Before: | ||
| > ```js | ||
| > import RSVP from 'rsvp'; | ||
| > | ||
| > await RSVP.all(promises); | ||
| > ``` | ||
| > | ||
| > After: | ||
| > ```js | ||
| > await Promise.all(promises); | ||
| > ``` | ||
| > | ||
| > 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 | ||
|
|
||
| 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 the `rsvp` module as deprecated in the API docs | ||
|
|
||
| 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. | ||
|
|
||
| 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 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 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` | ||
| - 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 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! Two things to note here: