NodeJS - Callbacks, Promises, or Async/Await?

in #nodejs4 years ago (edited)

Let's Discuss!

The point of this post isn't to say one way is better than another way but instead to open up a discussion about what people are using and why. Spoiler alert: I like the Async module from caolan and regular old callbacks.

My History

I have been building things with node.js for a few years now and absolutely love it. I started with version 0.8.6 (I think) and back then you had no choice but to use callbacks for everything. This quickly led to people getting mired down in "callback hell" due to poor flow control options and working with async code when they never had before. At least, that's what it was for me. It took a little time to get my head wrapped around async code, callbacks, and how you kept everything nice and tidy. It's something I still try to improve upon to this day.

Realizing this was not a sustainable development path, I started using the Async module (https://github.com/caolan/async). It has every flow control tool you can think of from Series to Parallel, Waterfall, Loops, you name it. I love the async module and use it in every project. If you've never used this module I recommend giving it a try. It has far too many features to list in this post. Click the link ya lazy ass!

Today

I use async.series a lot because you can write nicely structured, easy to read code with the added benefit of being able to name the steps.

What I mean is :

let myuser = false
async.series({
    lookupUser: (done) => {
        mydb.find("someuser", (err, result) => {
            if(err) return done(err)
            myuser = result
            done(err)
        })
    },
    emailUser: (done) => {
        email.send("whatever", "some email content", user.email, done)
    },
    logEmailSent: (done) => {
        mydb.insert("mailsent", user, done)
    }
}, (err) => {
    if(err) throw err
})

This is obviously pseudocode and doesn't actually do anything but it's meant to illustrate how you can organize things with async.series and name the steps. It also jumps to the end if you pass anything into the "done" method's first argument as an error. Much like the .catch() in a Promise chain.

You could write this simpler with async.waterfall by passing the user to the callback of each step.

async.waterfall([
    (done) => {
        mydb.find("someuser", done)
    },
    (user, done) => {
        email.send("whatever", "some email content", user.email, (err, emailSent) => {
            done(err, user, emailSent)
        })
    },
    (user, emailSent, done) => {
        mydb.insert("mailsent", user, done)
    }
], (err) => {
    if(err) throw err
})

Before you say "dude you don't need all those parentheses!" I know, I just like to surround my arguments in them even if it's only one argument. Having them there makes it easier for my brain to parse as I read the code.

Some people complain that this is too much code and that your code should be more terse. I argue that verbose code like this is actually better because when I go back to read it 6 months from now it reads very naturally and it takes very little time to see what it's doing.

The other contenders

Promises

Promises sold everyone on hating callbacks and promised (get it?) to be the savior of JavaScript developers everywhere because your code was easier to read and didn't suffer from callback hell! What they didn't tell you is that you can write the same awful "Christmas tree" code with .then inside of .then inside of .then until you want to punch whoever came up with promises. That said, I don't hate promises I just don't personally use them. I have not found that they solve problems that I have in my daily development or with working in a team of developers. I have colleagues that swear by them and want to use them so I've recently gotten more exposure to how and why they are good but I'm still not sold. The main argument I can see for them is that they solve many of the same issues that the Async module does but without needing an external dependency. That I can understand although I think it's a small thing.

Async/Await

The thing a lot of devs are talking about now. You can make your async code look synchronous! Holy balls, that's so cool!! (sarcasm). I don't get the drive to try to make async code look like sync code. There's a very small learning curve to node.js and async programming but once it clicks it's really not that difficult to do. I don't get the need to flatten everything out but it's likely just because I'm old and set in my ways with async module/callbacks.

Let me know what you think!

I read a lot and try to stay up on the new fetures available to me as a node.js guy so I appreciate the feedback from people that aren't as set in their ways as I am. Please tell me why I'm dumb for sticking to callbacks with the async module. Come show me how you do and do it better. Help me become a better, more modern developer.

Thanks Steemit, you guys are great!

-KK

Sort:  

Howdy Kap!

Consider adding the following tags to your post. I know some folks who watch these categories that will love this.

technology
programming
science

I am new to async and I think I prefer it too. The main problem I have with Promises, is that they try to use classic a Object Oriented Programming approach. When I was in college learning OO, they taught us in a very similar way. Like this...

Let's say our object is a car. The car has a start() method. It also has an IsStarted property. In order to start the cart, we fire the start() method and set the IsStarted property to true.

This way, all students could see how these relationships worked, and it helped make sense of Object Oriented Programming.

I see Promises trying to do the exact same thing. All of these tutorials on how to use them usually sound like this...

Let's say your mother makes you a promise. If you get good grades in school, she will buy you a new car. First you have to go and get good grades, which takes a while. etc.

The problem I have is exactly what you mention here.

So here is my premise and thesis point...

By forcing the OO concept into async calls with Promises, we end up trading in callback hell for christmas tree hell.

Asynchronous programming is not really an OO concept. It is more about using non-blocking, function pointers later on.

I also have never been able to like promises. I do thinking using bind() to flatten out and shorten long async methods is very helpful.

Can you post an example of what you mean?