What is an "async relationship"? / `{async: true}` vs. `{async: false}`

The Ember Data API mentions the concept of “async relationships”.
E.g., DS.belongsTo(..) and DS.hasMany(..) both allow to provide a hash with key async:

App.Post = DS.Model.extend({
  comments: DS.hasMany('comment', {async: true})
});

What is the difference of async being either true or false and what consequences does it have on application design?

5 Likes

As far as I know, when async is false Ember will fetch the related entity at the same time as fetching the parent entity. When async is true, it will fetch the related entities when you actually request them.

4 Likes

@Rengers, thank you! Let’s assume in the following that with {async: false}, any related entity will be in fact fetched eagerly (that’s how I understood your words).

Consider the following model/class:

App.User = DS.Model.extend({
  friends: DS.hasMany('user', {async: false})
});

friends is a reflexive association.
So does that mean that in the moment I find the first user with at least one friend, then this kicks off a cascade of recursive requests? E.g., in a big social network application, finding one user results in a million additional requests?

Good question, I’m not sure about that. You can try it and log the requests to see what happens of course ;). Maybe a more experienced Ember Data user can help out here.

The way I learned it was that async: false means that Ember Data expects to already have the related data as part of the original request, and async: true means it will need to make an additional request to get it (but it won’t necessarily make that request automatically).

In a simple relationship, where a User hasMany Stuff,

stuff: DS.hasMany('stuff', {async: false})

then Ember Data is expecting a request for User to get something like this in response:

{
stuff: [ { id: 1, user_id: 1 }, { id: 2, user_id: 1 }, { id: 3, user_id: 1 } ],
user: { id: 1, stuff_id: [1, 2, 3] }
}

but if the relationship was

stuff: DS.hasMany('stuff', {async: true})

then Ember Data is just expecting this:

{
user: { id: 1, stuff_id: [1, 2, 3] }
}

…and if you load the user and ask for user.get('stuff') then it will make a separate request for its related Stuff objects. So it may be more helpful to think of the async: value not as telling Ember Data how to fetch things, but as telling Ember Data how to expect things from the server.

I’ve only recently tangled with this, though, so I’d be happy to be corrected if I have it wrong.

18 Likes

Another thing to take into consideration is that

stuff: DS.hasMany('stuff', {async: true})

will always return a promise when you call myModel.get('stuff'), even if the server sends the data for stuff alongside myModel.

I believe @wycats said that {async: true} will become the default eventually - https://github.com/emberjs/data/issues/1443#issuecomment-26607013

2 Likes

Wonder where I find the downvote button!

2 Likes

Thanks @flashesofpanic, your described behavior is almost* what I observed on Ember 1.3.1 and Ember Data 1.0.0-beta.6:

Given:

App.Post = DS.Model.extend({
  comments: DS.hasMany('comment', {async: true})
});

then a query this.store.find("post", 12345) will result in an HTTP GET request to posts/12345. Ember Data expects as a response something like:

{
  post: {
    id: 12345,
    comments: [457459, 98989898, 3643434]
  }
}

(*… regarding “almost”: I’m using Ember 1.3.1 and Ember Data 1.0.0-beta.6. Notice in my above example, the server’s JSON response is expected to contain a key named comments. A key named comment_id or comments_id doesn’t work to my observation)

Regarding your “almost” - this is actually because you two are using different adapters.

The ActiveModelAdapter expects:

{
  post: {
    id: 12345,
    comment_ids: [457459, 98989898, 3643434]
  }
}

RESTAdapter (the default) expects:

{
  post: {
    id: 12345,
    comments: [457459, 98989898, 3643434]
  }
}
6 Likes

How this works now ?

async for me doesn’t work when I debugging data models on route ?

I’m need to load it the same time one of the data related with my visitor.

This works for me, however maybe there is better solution for that ?

// route file

model(params) {
        return Ember.RSVP.hash({
            visitor: this.get('store').findRecord('visitor', params.id).then(function(visitor){
                return Ember.RSVP.cast(visitor.get('quote')).then(function(){
                    return visitor;
                });
            })
        });
    },
1 Like

async= true relationships are not working even if its requested i have a .belongsTo relationship and im requesting the data of it but its not loading. Im using emberdata 2.18.x

@Thilina_Dinith_Fonse You’ll need to provide more details. My guess is the payload for the first model isn’t getting the id of the related model so it doesn’t know what to fetch, but it could be anything. Any errors? What do your API payloads look like for your primary model? And maybe your models and adapters too?

I am posting to keep the info up to date.

The best explanation I could find is this EmberMap post . Note, the author provides some arguments against async relationship, but async is the default option, a stable API and totally safe to use.

Async relationships (default)

Accessing the relationship will return a promise.

post.get('comments').then((comments) => {
  // now we can work with the comments
});

If the data is not available and the relationship is accessed (for example, from a template or a “consumed” computed property), Ember Data will automatically fetch the resources .

Sync relationships (from docs)

Ember Data resolves sync relationships with the related resources available in its local store, hence it is expected these resources to be loaded before or along-side the primary resource.

BelongsTo
export default DS.Model.extend({
  post: DS.belongsTo('post', {
    async: false
  })
});

In contrast to async relationship, accessing a sync relationship will always return the record (Model instance) for the existing local resource, or null. But it will error on access when a related resource is known to exist and it has not been loaded.

let post = comment.get('post');
HasMany
  export default DS.Model.extend({
    comments: DS.hasMany('comment', {
      async: false
    })
  });

In contrast to async relationship, accessing a sync relationship will always return a DS.ManyArray instance containing the existing local resources. But it will error on access when any of the known related resources have not been loaded.

post.get('comments').forEach((comment) => {
});

If you are using links with sync relationships, you have to use ref.reload to fetch the resources.

3 Likes