【问题标题】:How to return a promise composed of nested models in EmberJS with EmberData?如何使用 EmberData 返回由 EmberJS 中的嵌套模型组成的承诺?
【发布时间】:2014-06-06 01:51:51
【问题描述】:

环境

# 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,但是我有点未能创建正确的代码来链接承诺,并在最终then 修改返回的结果以匹配我的期望。

最后的想法

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

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

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

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

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

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

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

编辑 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! 中的工作解决方案!

【问题讨论】:

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


    【解决方案1】:

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

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

    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,因为这很有帮助。但是我不能(还)标记为接受,因为结果仍然不如预期。我更新了我的问题并包含了一个 JSBin 示例。如果你愿意看一看,请做。干杯
    • 嗨,我找到了问题所在。您的 anwser 有一个不明显的逻辑错误。您在回答问题方面帮助了我很多,所以如果您阅读我的编辑,然后更正您的回答,我会非常乐意将其标记为已接受。
    • 很好,我错过了它是一个集合数组的事实,没有测试的问题;)
    • 关于您的 anwser 的第二部分 - 我尝试在控制器中构建“行”,希望当承诺解决时 ember 会自动更新“行”,但这没有用:(
    【解决方案2】:

    你可以做什么:

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

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

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

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

    【讨论】:

      猜你喜欢
      • 2014-07-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-05
      • 2017-06-18
      • 2014-07-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多