等待流星收集完成后再进行下一步操作。

11

我有一个 Meteor 模板,应该显示一些数据。

Template.svg_template.rendered = function () {
  dataset_collection = Pushups.find({},{fields: { date:1, data:1 }}, {sort: {date: -1}}).fetch();

  a = moment(dataset_collection[0].date, "YYYY/M/D");
  //more code follows that is also dependent on the collection being completely loaded
};

有时它可以工作,有时我会收到这个错误:

Deps 在 afterFlush 函数中发生异常:TypeError: Cannot read property 'date' of undefined

我没有在任何上下文中使用 Deps。据我了解,集合被引用时加载还没有完成。

因此,我想找出如何简单地说“等待集合被找到再继续前进”。这应该很简单,但我找不到更新的解决方案。

3个回答

32

您说得对,应确保在客户端订阅集合的内容加载完毕后再执行依赖于该内容的代码。

您可以使用 Meteor 1.0.4 中引入的新模式来实现:https://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe

client/views/svg/svg.js

Template.outer.onCreated(function(){
  // subscribe to the publication responsible for sending the Pushups
  // documents down to the client
  this.subscribe("pushupsPub");
});

client/views/svg/svg.html

<template name="outer">
  {{#if Template.subscriptionsReady}}
    {{> svgTemplate}}
  {{else}}
    Loading...
  {{/if}}
</template>

在Spacebars模板声明中,我们使用包含outer模板来处理模板级别的订阅模式。 我们在onCreated生命周期事件中订阅发布,然后使用特殊的响应式辅助函数Template.subscriptionsReady,只有在订阅准备就绪(数据在浏览器可用)时才渲染svgTemplate。 此时,在svgTemplateonRendered生命周期事件中可以安全地查询Pushups集合,因为我们确保了数据传输到客户端:

Template.svgTemplate.onRendered(function(){
  console.log(Pushups.find().fetch());
});

或者,你可以使用 iron:router (https://github.com/iron-meteor/iron-router)。它提供了另一种设计模式来解决这个常见的与Meteor相关的问题,即将订阅处理移至路由级别而不是模板级别。

将该包添加到您的项目中:

meteor add iron:router

lib/router.js

Router.route("/svg", {
  name: "svg",
  template: "svgTemplate",
  waitOn: function(){
    // waitOn makes sure that this publication is ready before rendering your template
    return Meteor.subscribe("publication");
  },
  data: function(){
    // this will be used as the current data context in your template
    return Pushups.find(/*...*/);
  }
});

使用这段简单的代码,你将得到你想要的内容以及许多附加功能。你可以查看Iron Router指南,该指南详细解释了这些功能。

https://github.com/iron-meteor/iron-router/blob/devel/Guide.md

编辑于18/3/2015: 重新修订了答案,因为它包含过时的材料,但仍然受到赞同。


感谢您重新修改您的答案。Meteor发展迅速,有很多过时的信息。 - Tim Fletcher
1
重新修改了答案,以鼓励使用模板级别的订阅。这是一种设计模式,已经被纳入最新版本的Meteor中。 - saimeunt
1
应该是被采纳的答案。感谢iron:router和您的答案,我已经能够轻松解决我的问题。谢谢伙计! :-) - amahrt

3

这是我非常希望基本的Meteor文档直接解决的问题之一。它很令人困惑,因为:

  1. 根据API,您已经做到了正确的事情。
  2. 您会收到关于 Deps 的错误,但这并没有指向根本问题。

所以正如您已经发现的那样,在模板渲染时,数据还没有准备好。最简单的解决方案是假设数据可能尚未准备好。在示例中有很多这样的例子。从leaderboard.js中可以看到:

Template.leaderboard.selected_name = function () {
    var player = Players.findOne(Session.get("selected_player"));
    return player && player.name;
};

只有当实际找到player时,才会访问player.name。在CoffeeScript中,您可以使用soak来完成同样的事情。
saimeunt建议使用waitOniron-router对于这个特定的用例是很好的,但请注意,在应用程序中,您很可能会遇到数据在数据库中不存在或所需属性不在提取的对象上不存在的情况。
不幸的现实是,在许多这些情况下,需要进行一些防御性编程。

0
使用iron-router等待订阅是可行的,但我喜欢在类似于collections.js文件中集中管理订阅。相反,我利用Meteor的file load order在所有其他内容之前加载订阅。
以下是我的collections.js文件可能看起来像什么:
// ****************************** Collections **********************************
Groups = new Mongo.Collection("groups");

// ****************************** Methods **************************************
myGroups = function (userId) {
  return Groups.find({"members":{$elemMatch:{"user_id":userId}}});
};

// ****************************** Subscriptions ********************************
if(Meteor.isClient){
  Meteor.subscribe("groups");
}

// ****************************** Publications *********************************
if(Meteor.isServer){
  Meteor.publish("groups", function () {
    return myGroups(this.userId);
  });
}

我将 collections.js 放到了一个 lib/ 文件夹中,这样它就会在我的典型客户端代码之前被加载。这样一来,订阅就集中到了单个的 collections.js 文件中,而不是作为我的路由的一部分。这个例子还集中了我的查询,因此客户端代码可以使用相同的方法来提取数据:
var groups = myGroups(Meteor.userId());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接