A modern browser is required for security, reliability, and performance. Contact us.

Jul 06 2012

Incremental Async Recursive JavaScript Functions

Often I find myself needing to query multiple web API’s while iterating over a collection and then use that data to query another web service—fun stuff eh?

So in other languages I would simply wait until I received the message and then move on to getting the next message—blocking any other operations from happening in my code.

But with JavaScript we have closures! And so the fun begins.

But is this ever practical? Of course it is!

In this example I wanted to get my recent music and then display the album artwork for said artist. The trick is that album artwork was in a separate part of the api, thus 2 api calls were required.

“So what?”, you may be thinking.

Well the thing is, I wanted to optimize the number of calls that I was making to get the artist info, and the only way to do this was to wait for each artist request to come back to then check if that artist’s information had already been fetched.

To top it all off, I did not want to show anything until all the messages had been received.

So the flow of the code is as follows:

  • Get My Recent Tracks
  • Get the Unique Artists from the Tracks
  • Get each Artist’s Info
  • Display the Arists

Here is the code in Coffeescript:

$ ->
  getRecentTracks (tracks) -> getUniqueArtists tracks, (artists) -> getArtistInfo artists, displayArtists

## add each artist to the DOM
displayArtists = (artists) ->
  area    = $('#recent-music')
  for name, artist of artists
    area.append "<a href='spotify:search:"+artist.name.replace(/\s/g, '+')+"'><img src='"+ artist.image[2]['#text']+"'/></a>"

## Gets a list of unique artists
getUniqueArtists = (tracks, cb, artists = {}) ->
  if tracks.track.length
    artist = tracks.track.pop().artist['#text']
    if artists[artist] == undefined
      artists[artist] = {}
    getUniqueArtists tracks, cb, artists
    cb artists

## steps through each artist
## - warning recursive functions on large lists in JS can exceed stack
getArtistInfo = (artists, cb, key = 0) ->
  keys = Object.keys(artists)
  getArtist keys[key], (data) ->
    artists[keys[key]] = data
    if artists[keys[++key]] != undefined
      getArtistInfo artists, cb, key

## Gets artist info from last.fm
getArtist = (id, cb) ->
  $.getJSON('http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist='+id+'&api_key=1a7c2b9bdb474400ca73abd6002471cf&format=json', (d) ->
    cb d.artist

## Gets my recent tracks from last.fm
getRecentTracks = (cb) ->
  $.getJSON('http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=samccone&api_key=1a7c2b9bdb474400ca73abd6002471cf&limit=90&format=json', (d) ->
    cb d.recenttracks

—Sam Saccone (@samccone)