Ember等待关系加载数组与ember并发

时间:2016-12-03 21:51:47

标签: javascript ember.js ember-data

我正在尝试根据其相关模型之一对模型对象进行排序。

我有文档 hasMany LineItem LineItem belongsTo a 位置即可。我想按行位置对行项目进行排序。所以,如果我有这样的事情:

let lineItems = [
  {
    itemName: "One",
    location: {
      name: "Kitchen"
    }
  },
  {
    itemName: "Two",
    location: {
      name: "Kitchen"
    }
  },
  {
    itemName: "Three",
    location: {
      name: "Bathroom"
    }
  },
  {
    itemName: "Four",
    location: {
      name: "Bathroom"
    }
  },
  {
    itemName: "Five",
    location: {
      name: "Hall"
    }
  },
  {
    itemName: "Six",
    location: {
      name: "Hall"
    }
  }
];

我想去:

let groupedLineItems = {
  "Kitchen": [
    {
      itemName: "One",
      location: "Kitchen"
    },
    {
      itemName: "Two",
      location: "Kitchen"
    },
  ],
  "Bathroom": [
    {
      itemName: "Three",
      location: "Bathroom"
    },
    {
      itemName: "Four",
      location: "Bathroom"
    },
  ],
  "Hall": [
    {
      itemName: "Five",
      location: "Hall"
    },
    {
      itemName: "Six",
      location: "Hall"
    }
  ]
}

我已经弄清楚如何排序,但只有在所有关系都加载后才会排序。我无法弄清楚如何使用ember并发来等待所有关系在排序数据之前加载。

这是我的控制者:

~/app/controllers/document.js

import Ember from 'ember';
import _ from 'underscore';
import { task } from 'ember-concurrency';
const { computed } = Ember;

export default Ember.Controller.extend({
    init() {
        this.get('myTask').perform();
    },

    myTask: task(function * () {
        // Wait for locations to resolve.
        let lineItems = this.get('model').get('lineItems');

        yield this.get("secondTask").perform(lineItems);

        let groupedLineItems = yield _.groupBy(lineItems.toArray(), (lineItem) => {
            lineItem.get('location').then((location) => {
                return location.get('name');
            });
        });

        this.set("groupedLineItems2", Ember.Object.create(groupedLineItems));
    }),

    secondTask: task(function * (lineItems) {
        yield lineItems.toArray().forEach((lineItem) => {
            lineItem.get('location');
        });
    })
});

这是控制器上的WIP,不正确。在init上,第一个任务运行,但不等待为每个行项加载位置。我试图让它加载第二个任务中的所有位置,但显然不是正确的方法。因此在init上它将运行排序,但_.groupBy的函数将返回undefined,因为在该点未定义位置。然后,如果我在知道位置已加载后再次手动(使用模板上的按钮)再次运行任务(因为我在模板上的其他位置使用它们),它可以正常工作。

解决方案不必使用ember并发,它看起来就像是正确的工具,但我认为它也可以通过promises完成。

1 个答案:

答案 0 :(得分:1)

很棒的问题!

这是我将如何做到的。

models/document.js

import Ember from 'ember';
import Model from "ember-data/model";
import attr from "ember-data/attr";
import { belongsTo, hasMany } from "ember-data/relationships";

export default Model.extend({
  lineItems: hasMany('line-item'),

  groupedLineItemsPromise: Ember.computed(function () {
    return this
      .get('lineItems')
      .then(lineItems => {
        const locations = Ember.RSVP.all(lineItems.map(item => item.get('location')))
        return Ember.RSVP.hash({lineItems, locations})
      })
      .then(({lineItems}) => {
        return lineItems.reduce((result, item) => {
          const locationName = item.get('location.name')
          result[locationName] = result[locationName] || []
          result[locationName].pushObject(item)
          return result
        }, {})
      })
  }),

  groupedLineItemsProxy: Ember.computed('groupedLineItemsPromise', function () {
    return Ember
      .Object
        .extend(Ember.PromiseProxyMixin)
        .create({
        promise: this.get('groupedLineItemsPromise')
      })
  })
});

演示:https://ember-twiddle.com/3f7ee2371c424c456c48347774395191?openFiles=models.document.js%2C

请注意,您并非真的需要ember-concurrency。我使用好的旧计算属性来实现结果。

但问题是,这首先是错误的方法,ember-concurrency或不是:

  1. 此方法将生成大量网络请求,除非您使用记录不良的网络请求分组功能(并且您的后端支持它)。
  2. 如果任何请求失败,您的应用将进入不一致状态。整个并发任务/计算属性可能会失败,用户将无法重启失败的请求。
  3. 您的页面会过早呈现,没有任何内容。然后内容最终会出现。
  4. 当然,所有这三个问题都可以解决,但它是一个PITA。 这样做的正确方法是在路径的模型钩子中预加载关系。

    以一种只需几个网络请求即可获取所有必要记录的方式进行组织。使用JSONAPI过滤器或创建一个提供所有相关记录的自定义API端点。

    在最坏的情况下,仍然会执行大量请求,但是在路由模型挂钩中执行此操作,该挂钩将处理异步并将记录而非承诺记录到控制器/模板中。 预先加载相关记录后,您可以像访问同步一样访问关系