【问题标题】:Ember waiting for relationships to load in array with ember concurrencyEmber 等待关系以 ember 并发加载到数组中
【发布时间】:2016-12-03 21:51:47
【问题描述】:

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

我有一个 Document hasMany LineItem 和一个 LineItem belongsTo 位置。我想按位置对行项目进行排序。所以如果我有这样的事情:

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,不正确。在初始化时,第一个任务运行,但不等待为每个行项目加载位置。我试图让它加载第二个任务中的所有位置,但显然不是正确的方法。因此在初始化时它将运行排序,但 _.groupBy 的函数将返回未定义,因为此时位置未定义。然后,如果我在知道位置已加载(因为我在模板的其他地方使用它们)之后再次手动运行任务(使用模板上的按钮),它工作正常。

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

【问题讨论】:

  • 这不是“排序”。

标签: javascript ember.js ember-data


【解决方案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. 您的页面将过早呈现,没有内容。然后内容最终会出现。

当然,这三个问题都可以解决,但这是一个 PITA。 正确的做法是在路由的模型钩子中预加载关系。

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

在最坏的情况下,仍然会执行大量请求,但在路由的模型钩子中执行,该钩子将处理异步并将记录而不是承诺馈送到控制器/模板中。 一旦相关记录被预加载,您就可以访问关系,就好像它们是同步的一样

【讨论】:

  • 谢谢,这么好的答案!最后我意识到我需要做:return this.get('store').findRecord('document', params.document_id, { include: 'lineItems.location' }); 在我的模型钩子上。然后我可以这样做:groupedLineItems: computed('model.lineItems.@each.location', function() { const lineItems = this.get('model').get('lineItems').toArray(); return this.sortObject(_.groupBy(lineItems, (lineItem) => lineItem.get('location').get('name'))); })
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-17
  • 1970-01-01
  • 2018-07-05
  • 1970-01-01
相关资源
最近更新 更多