Ember减少了对hasMany和belongsTo查找的请求

时间:2014-02-18 21:38:28

标签: ember.js ember-data handlebars.js

我有以下三种模式:

ConversationApp.Post = DS.Model.extend(
  body: DS.attr()
  replies: DS.hasMany('reply', async: true)
  author: DS.belongsTo('user', async: true)
  likedBy: DS.hasMany('user', async: true)
)

ConversationApp.Reply = DS.Model.extend(
  body: DS.attr()
  post: DS.belongsTo('post')
  author: DS.belongsTo('user', async: true)
  likedBy: DS.hasMany('user', async: true)
)

ConversationApp.User = DS.Model.extend(
  firstName: DS.attr()
  lastName: DS.attr()
)

我的索引路线就是这个电话:

ConversationApp.IndexRoute = Em.Route.extend(
  model: (args) ->
    @store.find('post', page: 1) # => /api/v1/posts?page=1
)

在进行该呼叫之后,Ember开始获取第一页所需的所有用户 - 在第一页上共有17个(!)不同的用户请求(10个帖子)。以下是Ember向服务器发出的3个请求示例:

  • / API / V1 /用户/ 11375
  • / API / V1 /用户/ 4383
  • / API / V1 /用户的ID [] = 34588&安培; IDS [] = 7442&安培; IDS [] = 10294

我希望Ember只发出一个请求,请求第一页所需的所有用户:

  • / API / V1 /用户的ID [] = 34588&安培; IDS [] = 7442&安培; IDS [] = 10294&安培; IDS [] = 11375&安培; IDS [] = 4383

把手文件如下所示:

{{#each}}
  {{author.firstName}}
  {{#each likedBy}}
    [... removed for brevity ...]
  {{/each}}

  {{#each replies}}
    {{author.firstName}}
    {{#each likedBy}}
      [... removed for brevity ...]
    {{/each}}
  {{/each}}
{{/each}}

我该如何实现?

3 个答案:

答案 0 :(得分:9)

我知道这是一个旧线程,但这是使用DS.RESTAdapter的解决方案。

实现这一目标非常容易。您唯一要做的就是将coalesceFindRequests设置为true,如下所示:

App.ApplicationAdapter = DS.RESTAdapter.extend({
    coalesceFindRequests: true
});

或者如果您使用Ember CLI

// app/adapters/application.js
import DS from 'ember-data';

export default DS.RESTAdapter.extend({
    coalesceFindRequests: true
});

您可以在DS.RESTAdapter api docs

中详细了解相关信息 祝你好运! :)

答案 1 :(得分:3)

所以我对此嗤之以鼻。不幸的是,我不相信Ember-Data提供了开箱即用的这种功能。但它似乎并不难实现。我的方法是使用去抖动器。基本上,每次发出用户请求时,请求都会被放入池中。池填满,直到足够长的时间段(我的代码中为50ms)没有其他请求。在此之后,请求全部一起发送,池被清空。然后,当巨大的请求返回时,它被分解为较小的请求,可以用来完成最初在池中的请求。

请记住,我还没有对此进行测试,但这应该显示出一般的想法。

App.UserAdapter = DS.RESTAdapter.extend({
    _findMany: null,

    find: function(store, type, id) {
        return this.findMany(store, type, [id]);
    },

    findMany: function(store, type, ids) {
        this._findMany = this._super;

        // Create a promise, but keep the resolve function so we can call it later
        var resolve;
        var promise = new Ember.RSVP.Promise(function(r) {
            resolve = r;
        });

        // Let our debouncer know about this request
        this.concatenateRequest(store, ids, resolve);

        // Return a promise as usual
        return promise;
    },

    /**
     * The number of milliseconds after a request to wait before sending it.
     * Tweak this as necessary for performance.
     */
    debounceTimeout: 50,

    concatenateRequest: (function() {
        // All of the IDs currently requested in the pool
        var allIds = new Em.Set();
        // The pool of promises that is currently awaiting fulfillment
        var allPromises = [];

        // The ID of the last `setTimeout` call
        var timeout = null;

        // Takes the specified users out of the payload
        // We do this to break the giant payload into the small ones that were requested
        var extractUsers = function(payload, ids) {
            // Filter out the users that weren't requested
            // Note: assuming payload = { users: [], linked: {}, meta: {} }
            var users = payload.users.filter(function(user) {
                return (ids.indexOf(user.id.toString()) >= 0);
            });

            // Return payload in form that store is expecting
            return { users: users };
        };

        return function(store, ids, resolve) {
            // clear the timeout (if it's already cleared, no effect)
            clearTimeout(timeout);

            // Add the current promise to the list of promises to resolve
            allIds.addObjects(ids);
            allPromises.push({ ids: ids, resolve: resolve });

            // Set our timeout function up in case another request doesn't come in time
            timeout = setTimeout(function() {
                // Get the IDs and promises store so far so we can resolve them
                var ids = allIds.toArray();
                var promises = allPromises;

                // Clear these so the next request doesn't resolve the same promises twice
                allIds = new Em.Set();
                allPromises = [];

                // Send off for the users we know need
                this._findMany(store, ConversationApp.User, ids).then(function(payload) {                   
                    // Resolve each promise individually
                    promises.forEach(function(promise) {
                        // extract the correct users from the payload
                        var users = extractUsers(payload, promise.ids);
                        // resolve the promise with the users it requested
                        promise.resolve(users);
                    });
                });
            }.bind(this), this.get('debounceTimeout'));
        };
    })()
});
编辑:我使用单元测试设置了一个快速JSBin,它似乎运行正常。这是一个相当脆弱的测试,但它表明这个想法运作良好。

答案 2 :(得分:0)

我对Ember Data不太熟悉,但我认为这不是Ember的错。您可以尝试更改JSON API以侧载相关数据。

Ember Data使用JSON API作为json标准。它描述了关系的格式,以及如何在一个请求中发送数据及其关系。

如果一切正常(将来),请求/api/v1/posts中,如果你想侧面加载所有回复,你可以像这样构建json:

{
  "posts": [{
    "id": "1",
    "body": "post body 1",
    "links": {
      "replies": ["1", "2"]
    }
  }, {
    "id": "2",
    "body": "post body 2",
    "links": {
      "replies": ["3"]
    }
  }],
  "linked": {
    "replies": [{
      "id": "1",
      "body": "reply body 1"
    }, {
      "id": "2",
      "body": "reply body 2"
    }, {
      "id": "3",
      "body": "reply body 3"
    }]
  }
}

不幸的是它现在还没有用。我检查了最新的Ember数据源,找不到任何带有“链接”支持的代码。

如果您正在使用Rails和ActiveModelSerializer,另一种工作方式是使用嵌入数据,请参阅此Ember Data test了解嵌套数据格式。

JSON API和Ember Data都是未完成的项目。这意味着未来一切都可能发生变化,即使在今天,Ember Data也不完全支持JSON API。因此,如果您想在现实世界项目中使用Ember Data。您必须非常熟悉它的架构,并能够自定义适配器和放大器。序列化器供您自己使用。