meteormeteor-tracker

Meteor: Tracker.autorun and dep.changed causing infinite loop


I am using a new Tracker.Dependency for tracking several things, but it causes the autorun in the code below to run infinitely. What is wrong? The code below is okay once I separate the getSong and getSongId to depend on dep and dep2, instead of just dep.

SongManager = {
  dep: new Tracker.Dependency,
  dep2: new Tracker.Dependency,
  init: function(songId) {
    var self = this;
    this.setSongId(songId);
    Meteor.subscribe('song', songId);
    Tracker.autorun(function(){
      var songs = self.getSongCursor().fetch();
      if (songs.length > 0) {
        self.song = songs[0];
        self.dep.changed();
      }
    })
  },
  getSongCursor: function(){
    return Songs.find({_id: this.getSongId()});
  },
  getSong: function(){
    this.dep.depend();
    return this.song;
  },
  getSongId: function(){
    this.dep2.depend();
    return this.songId;
  },
  setSongId: function(arg){
    this.songId = arg;
    this.dep2.changed();
  },
};

Solution

  • The problem is that you're creating a circular dependency. I would recommend using ReactiveVar for this rather than working with the lower-level dependency API.

    meteor add reactive-var
    

    Then you can just do this:

    SongManager = {
    
      song: new ReactiveVar(),
    
      songId: new ReactiveVar(),
    
      init: function(songId) {
        this.songId.set(songId);
        this.computation = Tracker.autorun(_.bind(this.update, this));
      },
    
      update: function() {
        var songId = this.songId.get();
        Meteor.subscribe('song', songId);
        this.song.set(Songs.findOne(songId));
      },
    
      stop: function() {
        this.computation.stop();
      }
    };
    
    SongManager.init(oldSongId);
    SongManager.songId.set(newSongId);
    
    // After enough time has passed for the subscription to update and tracker to flush:
    var currentSong = SongManager.song.get();
    console.log(currentSong._id === newSongId); // true
    

    I also added a way for you to stop your autorun computation so it doesn't keep running in the background when it's no longer necessary. Note that since the subscription is run within an autorun, it will automatically be stopped and restarted when the songId changes. The update function will actually be run twice, but Meteor knows not to send two identical subscription requests.