8

环境

# 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.hashandRSVP.all函数,阅读官方 API 并观看这个演讲,但是我有点未能创建正确的代码来链接 Promise 并在最终then修改返回的结果以符合我的期望。

最后的想法

有人告诉我,像这样加载数据可能会导致许多 HTTP 请求,并且可能通过侧载更好地解决这个问题,但是:

  • 此时,我使用FixturesAdapter,所以 HTTP 请求不是问题
  • 我真的很想更好地理解 RSVP 和 Promises

这就是为什么弄清楚这应该如何正确完成对我来说很重要。

编辑 1:应用kingpin2k建议的更改

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

虽然代码有效,但结果是......意想不到的:

  • countries数组中我找到了countryregion对象。为什么?
  • 和对象似乎已加载,但区域没有(请参阅 JSBin 中的控制台日志结果)。country为什么region

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

所以我终于注意到我偏离了 Ember 的正义之路的地方。Kingpin2k 的 anwser 是一个巨大的进步,但它包含一个小错误:

return this.store.find('country').then(function(countries){
  // this does not return an array of regions, but an array of region SETs
  var regionPromises = countries.getEach('regions');

  // wait for regions to resolve to get the areas
  return Ember.RSVP.all(regionPromises).then(function(regions){
    // thats why here the variable shouldn't be called "regions"
    // but "regionSets" to clearly indicate what it holds

    // for this example i'll just reassign it to new var name
    var regionSets = regions;

    // now compare these two lines (old code commented out)
    //var areaPromises = regions.getEach('areas'); 
    var areaPromises = regionSets.getEach('areas');

    // since regionSet does not have a property "areas" it
    // won't return a promise or ever resolve (it will be undefined)

    // the correct approach would be reduceing the array of sets
    // an array of regions
    var regionsArray = regionSets.reduce(function(sum, val){
      // since val is a "Ember.Set" object, we must use it's "toArray()" method
      // to get an array of contents and then push it to the resulting array
      return sum.pushObjects(val.toArray());
    }, []);

    // NOW we can get "areas"
    var realAreaPromises = regionsArray.getEach('areas');

    // and now we can use Ember.RSVP to wait for them to resolve
    return Ember.RSVP.all(realAreaPromises).then(function(areaSets){
    // note: here again, we don't get an array of areas
    // we get an array of area sets - each set for the corresponding region
      var results = [];

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

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

4

2 回答 2

12

诀窍是您需要先解决某些承诺,然后才能访问这些记录上的属性。 Ember.RSVP.all接受一个承诺数组。 Ember.RSVP.hash接受承诺的散列。不幸的是,您处于无法构建您的承诺的情况下,直到先前的承诺得到解决(唉,在解决之前您不知道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}}
于 2014-04-22T02:28:55.183 回答
4

你可以做什么:

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

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

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

但是,我将原始 anwser 标记为“已接受”,因为它实际上回答了原始问题,并且此 anwser 只是供将来参考/其他用户使用的注释。

于 2014-04-24T12:06:50.937 回答