如何使用 EmberData 返回由 EmberJS 中的嵌套模型组成的承诺



Enviroment

# 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')

预期成果

路由的模型挂钩应返回一个对象数组。喜欢这个:

注意:缩进只是为了使示例更具可读性。

 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 并观看此演讲,但是我有点无法创建正确的代码来链接承诺,并在最终then修改返回的结果以符合我的期望。

结语

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

  • 目前,我使用FixturesAdapter,因此HTTP请求不是问题
  • 我真的很想更好地了解回复和承诺

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

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

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

虽然代码有效,但结果是...出乎意料

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

编辑 2:来自 Edit1 的意外行为的说明。

所以我终于注意到我偏离了安伯的正道。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中的工作解决方案!

诀窍是您需要先解决某些承诺,然后才能访问这些记录上的属性。 Ember.RSVP.all需要一系列承诺。 Ember.RSVP.hash需要一堆承诺。 不幸的是,你处于这样的情况:在之前的承诺得到解决之前,你无法构建你的承诺(啦,在countries解决之前,你不知道该得到哪个regions,在regions解决之前,你不知道该得到哪个areas(。 在这种情况下,您确实有一组连续的承诺要获取(尽管每个级别都有承诺数组(。 余烬知道要等到最深的承诺得到解决,并以该价值为模型。

现在我们需要假装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}}

你可以做什么:

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

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

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

但是,我将原始 anwser 标记为"已接受",因为它实际上对原始问题进行了标记,而这个 anwser 只是供将来参考/其他用户的注释。

最新更新