Start Date Release Date Release Versions PR link Tracking Link Stage Teams
4/23/2021
Accepted
  • Data

EmberData | Modernize PromiseManyArray

Summary

Deprecates reading, mutating or operating on an async hasMany relationship before resolving it's value in the application's Javascript code. Rendering an async hasMany in a template will continue to work as expected.

Motivation

PromiseManyArray is a promisified ArrayProxy that serves as a fetch wrapper when a user either implicitly or explicitly defines a hasMany relationship as async. It is the synchronous return value when a user access the async relationship on a record instance.

For instance, given the following Post model.

class Post extends Model {
  @hasMany('comment' { inverse: null, async: true })
  comments
}

A user today might access and map over the comments for a post in the following manner.

const commentProxy = post.comments;
const commentIds = commentProxy.map(comment => comment.id);

This often works because any resolved data for the relationship is immediately accessible via the ArrayProxy. However, if the network request initiated by accessing the comments relationship results in changes to what records are available, those changes would not be reflected in the commentIds. More often, users will await the relationship to ensure the data is fully ready for use.

const comments = await post.comments;
const commentIds = comments.map(comment => comment.id);

As the language and framework have evolved, better features have become available that negate the value of many of the methods that exist on PromiseManyArray. This RFC seeks to better align the way folks interact with an async relationship with the Octane paradigm, tracked properties, and async/await. Doing so allows us to take a major first step towards simplifying the use of array-like proxies throughout EmberData.

Detailed design

This RFC will reduce the API surface area of PromiseManyArray to just those methods needed to serve it's primary functions.

  • as a thenable/awaitable value (with promise state flags)
  • as a custom enumerable that for-convenience can be directly iterated by templates.

All other methods and properties on PromiseManyArray would be deprecated. Additionally, the methods addArrayObserver and removeArrayObserver on ManyArray will be deprecated.

Code examples, guides, and documentation should encourage users to resolve the relationship before operating on it, including avoiding the use of the remaining iterable APIs (which will be marked private).

This deprecation would target 5.0 and would become enabled no-sooner than 4.1 (although it may be made available before then).

We will keep the following properties and methods previously supplied by the PromiseProxyMixin

  • the public promise state flag isSettled
  • the public promise state flag isPending
  • the public promise state flag isRejected
  • the public promise state flag isFulfilled
  • the private property promise
  • the method then
  • the method catch
  • the method finally

But we will eliminate the private property reason.

We will keep the following property we had supplied on our own

  • the public property links
  • the method reload

While deprecating the following methods which delegated to the ManyArray

  • createRecord

As PromiseManyArray historically extended ArrayProxy, it inherited a large API surface from MutableArray EmberArray and EmberObject the totality of which will be deprecated except for:

  • the private property content
  • the property length
  • the public property isDestroyed
  • the iterator method objectAt (but which will now be marked private)

Finalized API of PromiseManyArray

Remaining API surface

  • content (private)
  • length
  • isDestroyed
  • objectAt (private)
  • links
  • reload
  • promise (private)
  • isSettled
  • isPending
  • isFulfilled
  • isRejected
  • then
  • catch
  • finally

Deprecated API surface

  • reason
  • createRecord
  • firstObject
  • lastObject
  • addObserver
  • cacheFor
  • decrementProperty
  • get
  • getProperties
  • incrementProperty
  • notifyPropertyChange
  • removeObserver
  • set
  • setProperties
  • toggleProperty
  • addArrayObserver
  • addObject
  • addObjects
  • any
  • arrayContentDidChange
  • arrayContentWillChange
  • clear
  • compact
  • every
  • filter
  • filterBy
  • find
  • findBy
  • forEach
  • getEach
  • includes
  • indexOf
  • insertAt
  • invoke
  • isAny
  • isEvery
  • lastIndexOf
  • map
  • mapBy
  • objectsAt
  • popObject
  • pushObject
  • pushObjects
  • reduce
  • reject
  • rejectBy
  • removeArrayObserver
  • removeAt
  • removeObject
  • removeObjects
  • replace
  • reverseObjects
  • setEach
  • setObjects
  • shiftObject
  • slice
  • sortBy
  • toArray
  • uniq
  • uniqBy
  • unshiftObject
  • unshiftObjects
  • without

How we teach this

As this deprecation targets 4.x, users would need to have upgraded to Octane paradigms before they could encounter the deprecations listed here. This means for most apps this should be as trivial as adding an await where necessary.

Drawbacks

It has become fairly common to interact with async hasMany relationships as if they are synchronous, and it is not easy to programmatically codemod a conversion. This may result in a sizeable amount of "find the missing await" for some applications.

Alternatives

Deprecate array-like APIs on ManyArray simultaneously. Why not? While this is something we seek to do as a follow up, that migration is a more difficult one than this which only pushes users to resolve async before interacting with the value. As the ManyArray has all these same methods in a non-deprecated state, existing code will continue to run as expected and without deprecation so long as that resolution is done. This is a first step which will make that deprecation easier when the time comes.