Meteor文档中的消息计数示例是如何工作的?

41

无法完全理解 文档 中的示例...我尝试以多种不同的方式运行它,以便观察其工作原理等。

如何订阅此内容?我们可以包含必要的客户端代码使其工作吗?

是否存在名为messages-count的集合?Room是消息的集合吗?我们可以在示例中包含集合定义吗?

对此有任何提示都将非常有帮助!

注意: 这是问题最初发布时的代码(2012年5月)。现在更简单。

// server: publish the current size of a collection
Meteor.publish("messages-count", function (roomId) {
  var self = this;
  var uuid = Meteor.uuid();
  var count = 0;

  handle = Room.find({room_id: roomId}).observe({
    added: function (doc, idx) {
      count++;
      self.set("messages-count", uuid, "count", count);
      self.flush();
    },
    removed: function (doc, idx) {
      count--;
      self.set("messages-count", uuid, "count", count);
      self.flush();
    }
    // don't care about moved or changed
  });

  // remove data and turn off observe when client unsubs
  self.onStop(function () {
    handle.stop();
    self.unset("messages-count", uuid, "count");
    self.flush();
  });
});
3个回答

52

感谢您促使我写出更清晰的解释。这里是一个带有我的注释的完整示例。我已经清理了一些错误和不一致之处。下一个文档发布将使用此内容。

Meteor.publish非常灵活。它不仅限于向客户端发布现有的MongoDB集合:我们可以发布任何我们想要的东西。具体来说,Meteor.publish定义了客户端可以订阅的一组文档。每个文档都属于某个集合名称(一个字符串),具有唯一的_id字段,然后具有一些JSON属性集。随着集合中的文档发生变化,服务器将向每个订阅的客户端发送更改,使客户端保持最新状态。

我们将在这里定义一个文档集,称为“counts-by-room”,其中包含名为“counts”的集合中的单个文档。该文档将有两个字段:一个带有房间ID的“roomId”和“count”:该房间中消息的总数。没有真正的MongoDB集合名为“counts”。这只是我们的Meteor服务器将向客户端发送并存储在名为“counts”的客户端侧集合中的集合名称。
为此,我们的发布函数需要一个“roomId”参数,该参数将来自客户端,并观察该房间中所有消息(在其他地方定义)。我们可以使用更有效的“observeChanges”形式来观察这里的查询,因为我们不需要完整的文档,只需要知道是否添加或删除了新文档。每当添加一个具有我们感兴趣的“roomId”的新消息时,我们的回调函数会增加内部计数,然后向客户端发布具有更新总数的新文档。当删除消息时,它会减少计数并向客户端发送更新。
当我们第一次调用observeChanges时,一些added回调函数将立即运行,对于已经存在的每个消息。然后,未来的更改将在添加或删除消息时触发。
我们的发布功能还会注册一个onStop处理程序,在客户端取消订阅(手动或断开连接)时清理。该处理程序会从客户端中删除属性并拆除正在运行的observeChanges
每当新客户端订阅"counts-by-room"时,发布函数都会运行,因此每个客户端都将有一个代表它的observeChanges运行。
// server: publish the current size of a collection
Meteor.publish("counts-by-room", function (roomId) {
  var self = this;
  var count = 0;
  var initializing = true;

  var handle = Messages.find({room_id: roomId}).observeChanges({
    added: function (doc, idx) {
      count++;
      if (!initializing)
        self.changed("counts", roomId, {count: count});  // "counts" is the published collection name
    },
    removed: function (doc, idx) {
      count--;
      self.changed("counts", roomId, {count: count});  // same published collection, "counts"
    }
    // don't care about moved or changed
  });

  initializing = false;

  // publish the initial count. `observeChanges` guaranteed not to return
  // until the initial set of `added` callbacks have run, so the `count`
  // variable is up to date.
  self.added("counts", roomId, {count: count});

  // and signal that the initial document set is now available on the client
  self.ready();

  // turn off observe when client unsubscribes
  self.onStop(function () {
    handle.stop();
  });
});

现在,在客户端,我们可以像处理典型的Meteor订阅一样处理它。首先,我们需要一个Mongo.Collection来保存我们计算出的计数文档。由于服务器正在发布到名为"counts"的集合中,因此我们将"counts"作为参数传递给Mongo.Collection构造函数。
// client: declare collection to hold count object
Counts = new Mongo.Collection("counts");

然后我们可以订阅。 (实际上,在声明集合之前,您可以订阅:Meteor将排队等待传入的更新,直到有地方放置它们。) 订阅的名称是"counts-by-room",它需要一个参数:当前房间的ID。我将其包装在Deps.autorun中,以便随着Session.get('roomId')的更改,客户端将自动取消订阅旧房间的计数并重新订阅新房间的计数。

// client: autosubscribe to the count for the current room
Tracker.autorun(function () {
  Meteor.subscribe("counts-by-room", Session.get("roomId"));
});

最后,我们已经获得了在Counts中的文档,并且我们可以像客户端上的其他Mongo集合一样使用它。任何引用此数据的模板都会在服务器发送新计数时自动重新绘制。
// client: use the new collection
console.log("Current room has " + Counts.findOne().count + " messages.");

2
非常清晰明了!非常感谢您抽出时间为我澄清这个问题! - Mike Bannister
2
请注意,在added函数中的self.flush();将在集合填充时将该订阅推送到客户端。想象一下,如果在“room_id”中有1,000,000条“消息”,那么您将收到从计数1开始到计数1,000,000结束的1,000,000个订阅。这将会使您的浏览器锁定相当长的时间!更不用说通过网络传输的数据量了... - matb33
@matb33,有没有更好的解决刷新问题的方法? - Leonhardt Wille
1
作为临时解决方案,您可以使用“setTimeout”技巧来限制在added函数中调用self.flush();的频率,例如: clearTimeout(t); t = setTimeout(function () { self.flush(); }, 10); - matb33
1
没事了,刚才看到你下面的代码了!看起来你已经解决了。 - matb33
如果你在没有发布的情况下声明了命名的客户端集合,会发生什么? - Merlin -they-them-

2
作为Leonhardt Wille所说,这个解决方案的缺点是Meteor会从Mongo服务器下载整个项目集合来进行计数。他在 gist.github.com/3925008 上提出了更好的解决方案,但是当插入新项时计数器不会更新。
以下是我的响应式解决方案:
集合:
Players = new Meteor.Collection("players");
PlayersCounts = new Meteor.Collection("players_counts")

服务器:

Meteor.publish("players_counts", function(){
    var uuid = Meteor.uuid()
    var self = this;

    var unthrottled_setCount = function(){
        cnt = Players.find({}).count()
        self.set("players_counts", uuid, {count: cnt})
        self.flush()
    }

    var setCount = _.throttle(unthrottled_setCount, 50)

    var handle = Meteor._InvalidationCrossbar.listen({collection: "players"}, function(notification, complete){
        setCount();
        complete();
    })

    setCount();
    self.complete()
    self.flush()

    self.onStop(function(){
        handle.stop();
        self.unset("players_counts", uuid, ["count"]);
        self.flush();
    });
});

客户:

Meteor.subscribe("players_counts")

Template.leaderboard.total = function(){
    var cnt = PlayersCounts.findOne({})
    if(cnt) {
        return cnt.count;
    } else {
        return null;
    }
}

从Meteor 0.6.6.3开始(可能更早),此代码失败:Exception from sub CfuTiQGacmWo5xMsb TypeError: Cannot call method 'listen' of undefined - russellfeeed
1
只是提供信息,这是 Meteor 0.6 之前的代码。请参见 @debergalis 上面更新的答案。 - Andrew Mao

0

刚刚找到了一个解决方案,可以避免 self.flush() 向客户端发送数千个更新 - 只需在计数时使用 _.debounce:

count = 0
throttled_subscription = _.debounce =>
  @set 'items-count', uuid, count: count
  @flush()
, 10
handle = Items.find(selector).observe
  added: =>
    count++
    throttled_subscription()
  removed: =>
    count--
    throttled_subscription()

这将仅在10毫秒内没有更改后设置计数并刷新订阅。

感谢#meteor上的@possibilities提供的提示。


1
这种解决方案的缺点是meteor会将整个集合下载到服务器上,因此如果您使用相对较慢的远程连接到mongoDB,则应用程序启动后会有明显的延迟(至少如果您的数据库中有10k个文档)。 - Leonhardt Wille

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