Enviroment
# Ember : 1.4.0
# Ember Data : 1.0.0-beta.7+canary.b45e23ba
型
我简化了我的用例,使问题更容易理解和分析。假设我们有 3 个模型:Country
、Region
和 Area
:
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.hash
和RSVP.all
函数,阅读官方 API 并观看此演讲,但是我有点无法创建正确的代码来链接承诺,并在最终then
修改返回的结果以符合我的期望。
结语
有人告诉我,像这样加载数据可能会导致许多HTTP请求,并且可能通过旁加载可以更好地解决此问题,但是:
- 目前,我使用
FixturesAdapter
,因此HTTP请求不是问题 - 我真的很想更好地了解回复和承诺
这就是为什么弄清楚应该如何正确完成这项工作对我来说很重要。
编辑 1:应用 kingpin2k 建议的更改
我为我的示例创建了一个JSBin,其中包含kingpin2k的anwser建议的更改。
虽然代码有效,但结果是...出乎意料:
- 在
countries
数组中,我找到了country
和region
的对象。为什么? country
和region
对象似乎已加载,但区域未加载(请参阅 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
(。 在这种情况下,您确实有一组连续的承诺要获取(尽管每个级别都有承诺数组(。 余烬知道要等到最深的承诺得到解决,并以该价值为模型。
现在我们需要假装regions
和area
是异步的,如果不是,你告诉 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 只是供将来参考/其他用户的注释。