Hi,
I’d like to get a public discussion going on the future of promises within the persistence libraries (i.e. ember-data and ember-model).
For the uninitiated: a Promise is an object that represents an eventual value, the retrieval of which can succeed or fail. You can specify success/error handlers on a promise object by using the .then
method. Promises can have many handlers chained together in sequence, allowing for a lot of elegant patterns for managing and encapsulating your async data and performing complex async operations. For more information, please see the RSVP GitHub, which is the Promise library used within and endorsed by Ember.
Ember is more and more adopting promises within its APIs, in particular in this upcoming router API facelift and within the persistence libraries ember-data and ember-model. To a certain degree, they’ve already been around for some time, but there are some aspects that are going to change, some of which aren’t totally agreed upon yet, so let’s talk about it.
The improvement that no one seems to disagree on is that instances of Model
(whether DS.Model
or Ember.Model
– from now on I’m not going to distinguish) should not have .then
method, which is presently causing a lot of conceptual issues problems due to the fact that a promise is not a value itself but rather a discrete attempt to retrieve a value. If a promise winds up in a reject state, it should remain in that state, and any concept of retrying or reloading belongs in another promise for that specific action/attempt to reload. If you leave .then
on the Model
instances, and an initial attempt to load the model fails, then any future calls to that .then
handler will instantly reject, among a plethora of other bizarre implications of mixing the concept of Promise with Entity.
The solution is to separate the Promise to load/materialize an entity from the Entity itself. Again, no one really disagrees on this; what’s open to debate is what the various .find
methods should return from now on.
Model.find
can either return a (possibly unloaded) Model object, or it can return a Promise. Whichever one is chosen, we’ll also need a way retrieve the other.
My particular take is that .find()
should return a promise that resolves with the loaded model or rejects with the error that prevented the load. To me it seems way more difficult to manage an object that may or may not be loaded, particularly when it comes to the Router, and handling all the different kinds of errors that may occur when you transition to a route but your data fails to load. I’m certainly biased by the work I’ve been doing in the aforementioned router facelift, since the error handling behavior I think is quite slick when you use promises rather than possibly unloaded objects. But in general, if you don’t use promises, you start having to attach/detach/manage event handlers for state changes in the model object, etc., which I feel is often a far less elegant solution when promises are an option.
But this strong opinion is weakly held, and I’d like to get the discussion rolling for a nice API for allowing the dev to choose their poison.
What follows are some examples of what the API could look like, depending on which direction we take it.
If .find
returns a promise:
Post.find(123).then(function(model) { alert(model.get('name')); });
// How to get a model object from the promise?
// Post.findRecord(123) ?
// I kind of like:
// Post.find(123).asRecord(); // returns a model instance
.find
returns a (possible unloaded) record (today’s approach)
var post = Post.find(123);
post.one('didLoad', function() {
alert(post.get('name'));
});
// How to get a loading promise?
// post.loadingPromise?
// post.loadingPromise()?
// post.load() ?