Ember 等待关系以 ember 并发加载到数组中

Ember waiting for relationships to load in array with ember concurrency

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

我有一个 Document,其中 hasMany LineItem 和一个 LineItem 属于一个位置。我想按位置对行项目进行排序。所以如果我有这样的东西:

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

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

好问题!

这是我的做法。

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. 如果任何请求失败,您的应用将进入不一致状态。大概整个并发task/computed属性都会失败,用户将没有机会重新启动失败的请求
  3. 您的页面将过早呈现,没有任何内容。然后内容最终会出现。

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

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

在最坏的情况下,仍然会发出很多请求,但在路由的模型挂钩中执行,该挂钩将处理异步并将记录(而不是承诺)送入 controller/template。 预加载相关记录后,您可以像访问同步关系一样访问关系