Skip to content


Subversion checkout URL

You can clone with
Download ZIP


Add optional callbacks for all asynchronous operations #579

zroadhouse-wsm opened this Issue · 11 comments

6 participants


Ember data presents an API with a synchronous appearance for operations that are fundamentally asynchronous.

For example, the following code seems plausible given the API:

// change your model
item.set('status', 'PROCESSING');

// Save the changes (async)

// Reload the items on a parent page
router.get('itemsController').set('content', store.find(Item, {status: 'PROCESSING'}));

// Transition back to the parent page

However, this doesn't work because store.commit and store.find are actually asynchronous operations that will get processed at a later point in time.

Implementing a working solution to the above problem is not trivial since you must temporarily add an observer on the item's isSaving property, then fire the store.find and transition, then remove the observer. The standard suggestions of adding hook methods on the model or subclassing the adapter are not appropriate because they are global in scope.

// change your model
item.set('status', 'PROCESSING');


var commitObserver = Em.Object.extend({
    committed: function(sender, key, value) {
        router.get('itemsController').set('content', store.find(Item, {status: 'PROCESSING'}));
        sender.removeObserver(key, this, 'committed');

item.addObserver('isSaving', commitObserver.create(), 'committed');


This has several disadvantages - the primary one being that we are observing something that is indirectly connected to the action that is being performed (commit -> isSaving) and we haven't really dealt with error cases.

The simplest solution is to modify asynchronous operations to accept a callback that is invoked when that operation completes.

The previous example could be restated like:

// change your model
item.set('status', 'PROCESSING');

// Save the changes (async)
store.commit(function(event) {
    // Success function

    // Reload the items on a parent page
    router.get('itemsController').set('content', store.find(Item, {status: 'PROCESSING'}));

}, function(event) {
    // Error function
    // display error message

    // Transition back to the parent page

The resulting code is as simple as you can write while acknowledging the asynchronous nature of the operations.


:+1: @zroadhouse-wsm !
I am still searching for a better solution than the workaround specified above..


@javierav Yes. The problem comes with transactions. Events that are described in the guide are for a model, not for a transaction. As a transaction can have several records, the solution is observes each of them. I would like to have the transaction callbacks (a promise maybe?).

I believe there should be callbacks for the transaction commit process:
Given a transaction that has several records, and guess we want to transition to another route when the commit succeed, and just display an error when any of them fail. I think this is a common use case.

We have to observe each record state, and if all of them are OK, then transition to the other route.
If one of them fail, then display a message.

Implementing this looks verbose, and not so simple...

PS: I am not confident with ember-data, so maybe am I missing something, or maybe this is not the right solution...


you're right, +1 for transactions callback


Since writing this, I found another case where I was forced to use a similar pattern as a workaround for missing a callback when loading a collection.

The key code here is the _deferredPageToItem function which is used twice - once to wait for the pagedContent data to be load to start, and a second time to wait for the array to finish loading. Note that it has to observe ".@each" on the array because it's hidden underneath layers of observers/etc that separate it from the original array that should have "isLoading = true" on it. (In CoffeeScript):

pageToItem: (item) ->
  content = @get 'pagedContent'
  if content?
    if content.length > 0
      itemIndex = content.indexOf item
      if itemIndex != -1
        @pageTo Math.floor((itemIndex / content.length) * @get('totalPages')) + 1
      # This is here to wait for EmberData to load the array content
      @_deferredPageToItem('pagedContent.@each', item)
    # This is here in case we try to set the item before the content is set
    @_deferredPageToItem('pagedContent', item)

_deferredPageToItem: (observing, item) ->
  controller = this
  pagedContentObserver = Em.Object.extend
    pagedContentChanged: (sender, key, value) ->
        sender.removeObserver(key, this, 'pagedContentChanged')
  @addObserver(observing, pagedContentObserver.create(), 'pagedContentChanged')

Instead of jumping through these hoops observing the target array, it would have been much easier to supply a callback to the store.find() API call.


@javierav - I saw the model events but didn't feel that they are appropriate because they are applied globally in all situations. In my case, I want to observer this load or that commit.


I could try a PR next week, it is an interesting feature


Is this related with #315?


I have begun work on something that will hopefully solve ^^:

Please read, and feel free to provide me with feedback:


@stefanpenner Status?

@stefanpenner stefanpenner was assigned

This is fixed by the 1.0 beta promises changes.

@wycats wycats closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.