如何使用Ember Data返回Ember JS中嵌套模型组成的承诺?

时间:2014-04-21 22:10:04

标签: javascript ember.js ember-data rsvp-promise

环境

# Ember       : 1.4.0
# Ember Data  : 1.0.0-beta.7+canary.b45e23ba

模型

我简化了我的用例,使问题更易于理解和回答。我们假设我们有3个模型:CountryRegionArea

Country:
  - id: DS.attr('number')
  - name: DS.attr('string')
  - regions: DS.hasMany('region')

Region:
  - id: DS.attr('number')
  - name: DS.attr('string')
  - country: DS.belongsTo('country')
  - areas: DS.hasMany('area')

Area:
  - id: DS.attr('number')
  - name: DS.attr('string')
  - region: DS.belongsTo('region')

预期结果

Route的模型钩子应该返回一个对象数组。像这样:

  

注意:缩进仅 ,以使示例更具可读性。

 Country I
   Region A
      Area 1
      Area 2
   Region B
      Area 3
 Country II
   Region C
      Area 4
 Country III
   Region D
      Area 5

目前的方法

App.MyRoute = Ember.Route.extend({
  model: function() {
    return this.store.find('country').then(function(countries){
      // promise all counties

      // map resolved countires into an array of promises for owned regions
      var regions = countries.map(function(country){
        return country.get('regions');
      });

      // map resolved regions into an array of promises for owned areas
      var areas = regions.then(function(regions){
        return regions.map(function(region){
          return region.get('areas');
        });
      });

      // do not return until ALL promises are resolved
      return Ember.RSVP.all(countries, regions, areas).then(function(data){
        // here somehow transform the data into expected output format and return it
      });
    });
  }
)};

错误

我得到的Error while loading route: TypeError: Object [object Array] has no method 'then'显然来自这段代码:

var regions = countries.map(function(country){
  return country.get('regions');
});
var areas = regions.then(function(regions){
// regions is not a promise

然而,这应该表明我遇到的真正问题:

问题

我需要countries决定获取regions,而我需要获得areas。我一直在检查RSVP.hashRSVP.all函数,阅读官方API并观看this talk,但是我有点无法创建正确的代码来链接承诺和最终{{1}修改返回的结果以符合我的期望。

最后的想法

我被告知加载这样的数据可能会导致很多HTTP请求,这可能会通过sideloading更好地解决,但是:

  • 此时,我使用then,因此HTTP请求不是问题
  • 我真的很想了解RSVP和Promises

这就是为什么我要弄清楚如何正确地做到这一点很重要。

编辑1:应用kingpin2k

建议的更改

我为我的例子创建了一个JSBin,其中包含了由kingpin2k的anwser建议的更改。

虽然代码有效,但结果是...... 意外

    {li>在FixturesAdapter数组中我找到了countriescountry个对象。的为什么吗
  • regioncountry对象似乎已加载,但区域不加载(请参阅JSBin中的控制台日志结果).. 为什么?

编辑2:解释Edit1中的意外行为。

所以我终于注意到我从Ember的正义道路误入歧途。 Kingpin2k的anwser向前迈出了一大步,但它包含一点错误:

region

所以..现在我终于正确地解决了所有对象(国家,地区,地区),并且可以继续我的工作:)

编辑3:此JSBin中的工作解决方案!

2 个答案:

答案 0 :(得分:12)

诀窍是,您需要先解决某些承诺,然后才能访问这些记录上的属性。 Ember.RSVP.all采用一系列承诺。 Ember.RSVP.hash接受了承诺。不幸的是,在你之前的承诺得到解决之前,你无法构建你的承诺(la,你不知道在regions解决之前得到哪个countries,而你在areas被解决之前,不知道要获得哪个regions。在这种情况下,你真的有一系列的承诺来获取(尽管每个级别的承诺数组)。 Ember知道要等到最深的承诺得到解决并将该值用作模型。

现在我们需要假装regionsarea是异步的,如果不是,你告诉Ember Data,信息将包含在country的请求中,或者在region的请求中,这些集合不会是承诺,因此下面包含的代码不起作用。

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

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

App.IndexRoute = Ember.Route.extend({
  controllerName: 'application',

  model: function() {
    return this.store.find('country').then(function(countries){
      // get each country promises
      var regionCollectionPromises = countries.getEach('regions');

      // wait for regions to resolve to get the areas
      return Ember.RSVP.all(regionCollectionPromises).then(function(regionCollections){

        var regions = regionCollections.reduce(function(sum, val){
            return sum.pushObjects(val.toArray());
        }, []);

        var areaCollectionPromises = regions.getEach('areas');
        //wait on the areas to resolve
        return Ember.RSVP.all(areaCollectionPromises).then(function(areaCollections){

          // yay, we have countries, regions, and areas resolved

          return countries;
        });
      });
    });

  }
});

所有这一切都说,因为看起来你正在使用Ember Data,我只返回this.store.find('country')并让Ember Data在使用时获取数据......这个模板可以在没有所有承诺的情况下工作代码,并将填充为Ember Data履行其自己的承诺(一旦它看到你试图使用数据,它将请求数据,好的延迟加载)。

{{#each country in model}}
  Country: {{country.name}}
  {{#each region in country.regions}}
    Region: {{region.name}}
      {{#each area in region.areas}}
        Area: {{area.name}}
     {{/each}}
  {{/each}}
{{/each}}

答案 1 :(得分:4)

你可以做什么:

如果你在这里,你可能会犯同样的错误:)

如果您需要一个复杂的对象树来显示您的路线,您还可以:

  • 如果您使用RESTAdapter,则可以在一个HTTP请求中加载数据。
  • 如果您使用FixturesAdapter(例如,在开发阶段)使用固定的数据集,您可以切换到LocalStorageAdapter - 就像您请求模型时一样,它会加载所有关联的模型。所以它就像简单的this.store.find('mymodel', model_id)
  • 一样简单

然而,我将原来的anwser标记为"接受"因为它实际上是原始问题的答案,而这个anwser只是一个未来参考/其他用户的注释。