« Back to home

The Asynchronous Journey

I love song covers. Yes, not all of them live up to capture all the spirit of the original recording, but getting through all the bad ones is suddenly worth it when you find the good ones. You might know the ones I'm talking about, they might be faster, cleaner, heavier sounding, different solo's, or all of the above. You know it's good when they take the song in a new direction that invokes a new vibe but with all the familiarity of the original. It's really what music is all about... taking what's out there and putting a different spin on it to put another mind in a trance.

Let me step back though, this project did not start with cover songs. It started with me wanting to save songs I heard on a radio station to a playlist. Outsourced curation if you will. Like any good developer I said to myself "screw manual labor, lets automate this", and I went in search of the spotify API. Turns out there's a good wrapper written in node I found.

Luckily this library already uses promises, otherwise I would have converted the api with bluebird. But for those who don't know what promises are, lets take another step back and recap. At the advent of asynchronous code, both in the browser and in node.js, any time you wanted something to happen out of the normal flow of you code, asynchronously, you had to provide a callback function. Something along the lines of what jQuery simplified it to be:

$.get('some url', function(response){
    console.log('got a response', response);
})

Now that's all well and good till you have to make the second asynchronous call based off the first, and then the second and fifth...

$.get('a one a', function(response){
    console.log('got a response', response);
    $.get('a two a', function(rabbits){
        console.log('got another response', rabbits);
        $.get('and a three a', function(madHatters){
            console.log('how deep can we go?', rabbits);
        })
    })
})

Oh, and good luck handling errors in there. So along came promises. Instead of providing a callback to execute, a promise object is returned which has a .then method for getting the value of whoever pinky promised us a value. But like any good pinky promise, things still will go wrong and errors need to be handled. However, our code is looking a lot more linear now:

var finalPromiseChainResult = 
    $.get('first thing I need')
    .then(function(first){
        return $.get('second piggy');
    })
    .then(function(pigsNblankets){
        return $.get('krinkle stiltskin');
    })
    .then(function(crumpets){
        return 'are we done yet?';    
    })
    .catch(function(error){
        console.log('Error on the chain! Error on the chain!', error);
    })

This is definitely a step up from the first example and there are tons of libraries that implement the A+ Promise Spec. This is still not good enough though. Besides the boilerplate, you have to make sure that each function in the chain actually returns a value or a promise otherwise the chain will merrily carry on without waiting for anything. Secondly, if we needed to use the results from call's one and two in the third function we'd have to save them to variables in a higher scope. Another error waiting to happen. Last and most importantly is something even more devious, we've lost three very important keywords from the language: try, catch, and return. Using these constructs for flow control is a very natural part of C derived languages. However, since the control needs to happen inside of a nested function, returning early from the containing function is a lot more difficult.

So this is where I was at trying to use the node spotify api to look up users, songs, add things to playlists etc. I needed results from earlier in the promise chain later down the line, I needed to decide what to do when results came back, and of course handle those pesky problems as they're thrown back at me. Why was this feeling so complicated?

Luckily around this time I was complaining about how hard this was to a friend and got the pro tip that I should be using the hot off the presses ES7 Async Await spec. I had already been using babel for a little while admiring all the ES6 features like arrow functions and an easier class syntax but this blew them all away.

Compare the above code to below

async function(){
  try{
    let firstResult = await $.get('one');
    let secondResult = await $.get('two');
    let thirdResult = await $.get('three');
  }catch(e){
    console.log(e);
  }
}

That's all you have to do. Flow control works as expected. Variables are scoped as expected. It works with all A+ promise libraries. And you can use this today. Mind blown.

The amount of complexity this reduces in your code is staggering. Your code should be simple and easy to understand. Either your code is so simple that there are no obvious bugs, or your code is so complex that there are no obvious bugs. You want the former, trust me.

After I put my desk back together after flipping it over in excitement, I easily got the radio tracks flowing smoothly into my playlist and moved right on to an idea I'd had for awhile. Cover songs. With my new found power it was trivial to find all the songs on a playlist, look up all the songs that contain the names of the original songs but aren't by the same artist. Now that the mental stack has been popped back to nearly empty, I share with you the power to find a bunch of songs with names you recognize, artists you might not, and false positives that might surprise in a delightful sort of way.

Check out the full source code to see how it all works, and drop a comment if you have ideas on how to improve it.

Click the log into spotify link, pick a playlist and coverify it! If something goes wrong it'll show the login link again so go ahead and try it again.

Spotify Login

Playlists

Comments

comments powered by Disqus