Building a autocomplete on top of iTunes API


#1

Right now I’ve built a mizzao:autocomplete search solution on top of the iTunes API, it’s working by creating two client-side collections and populating these with two subscriptions:

Tracker.autorun(function() {

  if (Session.get('showQuery')) {
    Meteor.subscribe('showSearch', Session.get('showQuery'));
  }

  // Episode List
  if (Session.get('feedUrl')) {
    Meteor.subscribe('episodeListFromUrl', Session.get('feedUrl'));
  }
});

Session variables for showQuery are setup on keyup:

Template.submitEpisode.events({
// Query Show
  'keyup input#show-select': function(event, template) {
    event.preventDefault();

    var query = template.$('input#show-select').val();
    if(query) {
      Session.set('showQuery', query);
    }
  }
});

And on the server I have two publications. For show I’m just querying the iTunes API per query change (keyup) like this:

/*
*	Search iTunes API for Show Names
*/
Meteor.publish('showSearch', function(query) {
	var self = this;

	try {
		var response = HTTP.get('https://itunes.apple.com/search?', {
      params: {
        term: query,
				media: 'podcast',
				limit: 6
      }
    });

		// iterate through result
		_.each(response.data.results, function(item) {

			// create show document
			var show = {
				collectionName: item.collectionName,
				feedUrl: item.feedUrl,
        trackId: item.trackId,
        artistName: item.artistName,
        primaryGenreName: item.primaryGenreName,
        genres: item.genres,
        country: item.country
      };

			self.added('showNames', Random.id(), show);
		});

		self.ready();
	}
	catch(error) {

		// throw error
		throw new Meteor.Error(error);
	}
});

And for the episodes I’m querying the feedUrl (XML) like this:

// Publish Episodes from Feed URL (XML)
Meteor.publish('episodeListFromUrl', function(feedUrl) {
	var self = this;

	try {
		var feedUrlResponse = HTTP.get(feedUrl);
		var result = xml2js.parseStringSync(feedUrlResponse.content);

		// parse
		_.each(result.rss.channel[0].item, function(episode) {
			self.added('episodeNames', Random.id(), episode);
		});

		self.ready();
	}

	catch (error) {

		// throw error
		throw new Meteor.Error(error);
	}
});

This is performing horribly, and I’m getting (after repeated / regular use) insane CPU spikes, I’ve tried doing some light performance analysis with Kadira and I’m assuming that the problem is the amount of subscriptions, given that I’m basically subscribe per keystroke (and the more I type and search the more subscriptions). I would love any kind of pointers for how to do this. Should these calls be Method calls instead? If so, would love just a quick explanation how I could make the returns work with mizzao:autocomplete or any other autocomplete package. Any help much appreciated!


#2

Off the top of my head, you might try storing the subscription in an instance variable and just updating it in the autorun function. Sometihng like this:

Template.templatename.onCreated(function(){
    var instance = this;
    instance.autorun(function(){
         instance.subscription = Meteor.subscribe('showSearch', Session.get('showQuery'));

         if (instance.subscription.ready(){
                  //do stuff here
         }
    }

});

I think this prevents you from having a ton of different subscriptions. I pretty much use this model for every thing I’ve done recently and it has worked out very well. Granted I haven’t been updated my subscriptions on keystroke.

You might also want to check out Meteor Toys, which has a tool that lets you see all of your subscriptions on the front end while developing.


#3

Fantastic, great idea. I’ll try this tomorrow. Meteor Toys was a great tip I actually hadn’t thought of, did some preliminary tests today and as soon as those queries start coming in it’s wavering at 15+ subscriptions for each input field.


#4

Good luuuuuuuuuuuuuuck!