Callbacks, Promises, and ES6

Share on FacebookShare on Google+Tweet about this on TwitterShare on Reddit

I come from the land of PHP, where calls to external resources are synchronous.  This makes things like making web requests or database calls fairly easy.  Especially if you’re making these calls in a loop, or using the results from the first call as input for the second.

You can see from the above example, it’s super straightforward to make these types of calls.  You query the database, wait while it’s working, and put the result in the variable once you’re finished.  These types of calls to external resources block (or pause execution), but that’s usually not too big of a deal.

When I started working in node.js though, everything I know about external resource calls got thrown out.  Enter asynchronous processing.

In javascript, making a call to an external resource doesn’t block.  The code requests the resources and keeps chugging along. It has no regard to if that call finished, if there was an error, of what the result of the call was.  Being able to write asynchronous code is great though.  If you need to kick off a bunch of different tasks, you don’t want each one to block while it works, they can all start right after the previous one does.  But how do you actually get your results?

Anyone who’s done some work in javascript knows that’s not going to work.  Since javascript doesn’t wait for doAsyncThing  to finish, data will be undefined.  So what’s the solution?  Callbacks.

You can see from the above code that the first parameter we pass to our example asynchronous function is the data, and the second is the callback.  What’s a callback?  Well it’s a function that we pass to another function that says what to do when we eventually get data back from an asynchronous call.  In this example, we log an error if there is one, but otherwise we log the data to the console.  I’m not going to talk about callbacks much more, so if you’re still fuzzy on their use, there’s a number of great tutorials online that explain them in more detail.

So now that we can make calls to external resources, AND get the data back, everything is wonderful.  We can make calls all day, and use results of functions to call other functions.  Assuming each call has a callback…

Eek.  That’s pretty ugly.  Since every async call needs a callback, now we have nested callbacks.  This isn’t even a very in-depth example, but you can already see what a nightmare this would be on any substantial sized project.  This code snippet is a classic example of what javascript programmers refer to as “Callback Hell“.  It’s a bad thing for a number of reasons, but most importantly is that it’s really hard to read.

So what can be done about it?  It’s called promises.

In the example above, I’ve rewritten my async function to return a promise instead of a callback.  A promise is, simply put, an agreement that we will return data at a later date.  When a promise returns data as expected, it’s considered “resolved”.  If it encounters a problem, the promise is considered “rejected”.  You can see that instead of writing an anonymous function to handle our output, we’re using .then  to handle it.  This lets us chain promises more easily, and then use a .catch  to catch any errors that may have occurred.  Using promises will prevent callback hell if used correctly, but what if you don’t control the function that is making your async calls?

Bluebird has a number of really great features, but one of the best is its promisification.  It will let you take a function (or set of functions) that’s written for callbacks, and “promisify” it to use promises instead.  All of this happens without any extra work on your part.  How great!  You can see in the example above that my doAsyncThing  function looks the same as it did before I rewrote it, and it definitely still accepts and returns a callback.  However, I’m promisifying it, and saving the new version as doPromiseThing .  When I call it, I use it just like I did in my previous example, and it’s like it was written with promises the whole time.

This is a huge step forward from where we started with callbacks, and specifically nested callbacks, but it gets easier still.  ES6 (essentially the new version of javascript) is coming out soon, and it’s packed with tons of new functionality and ways to make your life easier.  I don’t discuss all the new features, but I will briefly talk about two that I love.  Generator functions and the yield keyword.

Generator functions are functions that can pause and resume their execution.  This let’s us effectively wait while something is happening, and pick back up once it’s finished.  The yield keyword is essentially the way that you return values back when using generator functions.  That all sounds well and good, but how do we use it?

You can see in the above example that my async function is the same as it’s been, no changes there.  What has changed is that we’re using a new package (co) that will help us with performing async functions and yielding.  We’re still using bluebird to promisify our async function, but instead of using a .then  to process our results, we’re assigning them directly to data  with the yield keyword!  Wrapping the entire code block with co  is what let’s us do this.  While we’re waiting for our async function to complete, the code pauses and waits for the results.  This makes our code significantly more readable.

But what does it look like if we want to use results from one async call to make another, like with our “Callback Hell” example?

More yields.  That’s really all there is to it.  No chaining .then , no nested callbacks, just yield.  It’s super easy to read, and not much more difficult to write.

Until recently, these ES6 features were considered experimental, and not supported by the stable release of node.js.  Now that node.js and io.js have merged though, the use of generator functions (and much more) are included in node.js without having to pass any special flags.

I hope you enjoyed this, and can’t wait to get out there and use yield yourself.

Leave a Reply

Your email address will not be published. Required fields are marked *