JavaScript Asynchronous Architectures: Events vs. Promises

Javascript Asynchronous Architecture: Events vs PromisesI can’t go a single week without reading another article talking about promises. I’m not talking about when you promise your child that you’ll be there for his baseball game. I’m talking about a JavaScript concept that makes it simple to react to the completion of asynchronous actions without indenting ten levels when you need to perform one asynchronous action after another. While working on a Backbone application, I tried to use promises in my main asynchronous processes, and I’m not sure it measures up to my previous event hub architecture. Let’s compare!

Before I get into why I prefer the event hub, at least for my own application, I’d like to go over each of the methodologies a little so you can understand them better, just in case you haven’t heard much about them.

Promises and the Deferred Object

These have become all the rage these days, and for good reason. Rather than creating a function that allows you to send in a callback that is run when an action finishes, the function returns a promise object. Upon this promise object you can now call something like done and send a callback into it that runs when/if the promise reaches a “done” state. A promise is created by a Deferred object. First you create a Deferred object and then return deferred.promise(), which gives you your promise object. The deferred is used to update the status of the asynchronous action. For example, when the action is completed you would call deferred.resolve(). When this is called, the promise will run all of the callbacks that were registered to it through the done, then, and always methods.

Let’s look at some examples to compare traditional callbacks to using promises. These are taken from the Parse Blog because they do a pretty decent job of demonstrating the usefulness of using promises:

// Traditional example using nested 'success' callbacks
Parse.User.logIn("user", "pass", {
    success: function(user) {
        query.find({
            success: function(results) {
                results[0].save({ key: value }, {
                    success: function(result) {
                        // the object was saved.
                    }
                });
            }
        });
    }
});

// Another traditional example using plain callbacks (without 'success')
Parse.User.logIn("user", "pass", function(user) {
    query.find(function(results) {
        results[0].save({ key: value }, function(result) {
            // the object was saved.
        });
    });
});

As you can see, in either case you end up nesting deeper and deeper with each action you perform. Here’s what it would look like if all three of the methods used in the above example returned promises.

// Promises example using 'then'
Parse.User.logIn("user", "pass").then(function(user) {
    return query.find();
}).then(function(results) {
    return results[0].save({ key: value });
}).then(function(result) {
    // the object was saved.
});

As you can see, no matter how many actions we perform, the indentation only goes one level deep. The way it is written, it reads quite easily: “login, then find, then save, then… whatever we do when it’s saved.”

To do the chaining as it is done above, we need to use then because then returns a new promise that is resolved either when the callback function returns a non-promise or the promise that the callback function returns is resolved.

For more on promises, you should check out the Q library and its documentation. jQuery also has a promises implementation, but as noted in an article by Domenic Denicola, it’s broken a bit. I still tend to use jQuery’s implementation because I don’t need an additional library and thus far it suits my needs.

Events and the Event Hub

I’ve already talked about using Event-Based Architectures, but I’ll still touch on it a bit more here. Rather, I’m going to give more concrete examples here. Using the event-based architecture is similar to the traditional callback way of doing things, except that you register the callback beforehand and it persists for use when an event is triggered again later. We’re going to use Backbone’s event system because it is similar to what I’m trying to use in my application. If you’re not familiar with Backbone, I suggest going through my screencast series on Backbone, but beware that newer versions of Backbone make this somewhat obsolete. Don’t worry, I’ll put together something to show you all the changes after 1.0 is released.

The example below is part of an application that starts and stops servers that run on the back end. The client app makes calls to the back end to start a server.

// The view will do something when a model finishes doing something asynchronous
ServerView = Backbone.View.extend({
    initialize: function() {
        this.model.on('started', this.serverStarted, this);
    },

    serverStarted: function() {
        // change something about view to indicate to users that
        // the server is running
    },

    startServer: function() {
        this.model.start();
    },
    ...
});

Server = Backbone.Model.extend({
    initialize: function() {
        // React when the application lets us know a server was started
        AppEvents.on('server:started', this.started, this);
    },

    start: function() {
        // Using a utility class, make a call to the back end to start the server.
        // When a success message comes back from the back end, the utility will
        // trigger an application-wide event to inform the entire system that a
        // server has been started.
        communicator.startServer(this);
    },

    started: function(serverID) {
        if (serverID == this.get('id')) {
            // trigger the 'started' event
            this.trigger('started', this);
        }
    },
    ...
});

server = new Server();
view = new ServerView({model:server});

There’s a lot more to this example even though it essentially only does one thing. One thing I didn’t mention in the code is how the view’s startServer method is called. We’ll assume it’s done via user interaction, such as clicking a “start server” button.

As you can see, in the initialize functions of each of the above ‘classes’, we register our event handlers. This only happens once, so even if we start (and stop – even though I didn’t show code for stopping) a server multiple times, the handlers already exist and are ready to handle any event.

The Comparison

Do you see the awesome differences that events made?

  1. The start functions on the view and model are very small and only do one thing: start the server (according to their respective abstractions).
  2. The whole system is now able to know about the server starting. Nothing needs to have knowledge of any of the individual server models, but can still react when one of them starts.

The code examples for the promises pretty much showed some procedural programming. This is all well and good, but what about object-oriented programming? Objects’ methods need to be succinct, and if a single method is handling everything that is shown in that example, it may be a good idea to refactor.

I also like the event-based architecture better in this instance because in my real application I’m using WebSockets to tell the back end to start the server. WebSockets are already event-based, so it seems to make sense to use events for handling these sorts of things.

Finally, in this example, we have several layers of abstraction (plus one more in my real application), so for the most part, I’m just passing the promise all the way back and no one is using it until it gets to the view, in which case the promise would be used to do more than start the server, so it shouldn’t be in the startServer method.

In all fairness, you can send a callback function with WebSockets (at least with Socket.IO; I’m not sure about WebSockets themselves) and use the callback function to resolve the promise as well as alert the rest of the system. In other words, you can use a combination of promises and events, but this makes it difficult to decide which is a better practice in each individual situation. However, as my application grows, I may end up needing to do just that. Time will tell.

Conclusion

The reason I wrote this article is because I recently spent much time arguing with myself on this very issue. How should the asynchronous actions be implemented in my application? I thought about it a lot, and even as I wrote this, I thought about it even more. They’re both great techniques and deserve to be looked at. In the end, this article is more to get you thinking about your asynchronous design decisions than it is to argue for one methodology over the other. God bless and happy coding!

About the Author

Author: Joe Zim

Joe Zim

Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.


  • Daniel_Automitive

    Thanks for the good review, I was in the same boat (used event hooks a lot with databinding in silverlight – before i moved to javaScript). However the clincher for promises is the ability to do the following(from my latest app) – it allows two independent asynchronous methods to run, and when they have both finished, a complex UI update gets called. I don’t know how I would have done that in this simple way with events.

    var foodRequest = data.accessData
    (‘/foodEntryDetails/’).then(function (response) {
    currentDay.foodEntryDetails = response;
    });

    var measurementRequest = data.accessData(‘/measurementEntryDetails/’ ).then(function (response) {
    currentDay.measurementEntryDetails = response;
    });

    all([foodRequest, measurementRequest]).then( display.updateDiary );

    • http://www.joezimjs.com Joe Zimmerman

      Yup, promises definitely aren’t bad. As I stated at the end:
      “In the end, this article is more to get you thinking about your
      asynchronous design decisions than it is to argue for one methodology
      over the other”

      Promises are a great solution and can be very useful in some circumstances. The same goes for events. I just want to make sure people realize that promises aren’t the only way to go. I’m glad to hear that both methodologies have been useful to you.

    • R’phael Spindel

      The way that you synchronize multiple events within an event-driven architecture is with a Finite State Machine. One (or the other) of the prerequisite event triggers the FSM to go into an intermediary state where it waits for the other event(s) to arrive, upon arrival of the final prerequisite event, the FSM then can move into the next state. There are many FSM plug-ins for backbone if you want FSM in the backbone architecture, they are listed on the wiki page, YMMV try some of them out or implement one of your own FSM’s in JS. I have a home-rolled FSM which is very flexible and can use in any project which I have been using for years and which is based on this excellent article http://www.ibm.com/developerworks/library/wa-finitemach1/

  • http://kudris.com/ Антон Кудрис

    I didn’t really understood that passage about sending callback over websockets (I mean if callback depends on the environment, then I don’t see how it’s possible to pass it using websockets… I believe socket.io would stringify everything that you try to send over websockets)

    As for callbacks vs promises, don’t forget about performance costs. Callbacks are faster. Much faster actually ( https://github.com/medikoo/deferred#performance ). And I hardly think you can compare events to promises. Just image that your app has a little bit more complicated logic where you’d need to sync several async tasks — in that situation you’d be very uncomfortable with events.

    Events and promises are just for different things. Promises are for dealing with async tasks and events for messaging.

    • http://www.joezimjs.com Joe Zimmerman

      Callbacks via Socket.IO: Read the section titled “Acknowledgement” on this page http://www.joezimjs.com/javascript/plugging-into-socket-io-advanced/

      Generally performance difference, no matter how big percentage-wise is negligible from a user’s perception. However, that doesn’t mean it shouldn’t be considered if performance becomes a noticeable issue.

      Generally, you are correct; events and promise are designed for different things, but try thinking about like this: promises and events are both used for reacting to certain conditions changing. If that condition is a response back from the server, then promises are generally the way to go, but that’s not always true. In my application, for example, if I get a response back that informs me that the server has indeed been started, then if I just used promises, then only the module that told the server to start would be aware of the change, but through events, everything that cares can be notified and react accordingly. Granted, the server being started and receiving the response back from the server aren’t actually the same thing, but they carry the same information with them.

  • http://twitter.com/pedrasmachado Ricardo Machado

    Hi Joe,

    Nice article… Just one thing, there’s a single quote missing on the line 33 of your ‘event hub’ example :)

    • http://www.joezimjs.com Joe Zimmerman

      You’re absolutely right. Thanks for informing me.

  • http://www.adrianmiu.ro/ Adrian Miu

    I think events are suitable when
    1. you don’t know or don’t care who handles the result of the “promise” (you don’t care what, if anything, happens on click but you do want to process the results of an ajax request)
    2. you have a single events that are not part of a chain (it wouldn’t look good for the “login_success” event to trigger “load_user_profile” and you don’t want to trigger “load_user_profile” anywhere else beside after a “login_success”)

    I’ve build an app using events and no matter how good you name your events there’s a high chance another developer working on the same project to ask “Where/how did this got executed?”. From this point of view I think promisses are easier to debug.

    • http://www.joezimjs.com Joe Zimmerman

      The “how did this get executed” question is actually isn’t so horrible. First of all, good documentation will help with that. Second of all, that means you have good decoupling. Of course, good documentation isn’t always easy to produce and sometimes coupling isn’t bad, but I like this kind of decoupling.

      However, you do have to limit the events sometimes. You shouldn’t be triggering events that are “command” events, like an event named “doThis”. Events should be just that: events. They should say something happened, not tell others to do something.

  • http://twitter.com/juandopazo Juan Ignacio

    I think those of us writing promises implementations are doing a bad job at explaining what promises are for. My opinion is that you as a user should never have to interact with promises using then(). then() should a way for promise-consuming libraries to interact with each other and to provide fluent APIs. You should still use callbacks and events as usual.

    This means that your promises example should’ve looked like this:


    Parse.User.logIn("user", "pass", function (err, user) {
    query.find().item(0).save({ key: value }, function (err) {
    // moar code
    });
    });

    The API should be chainable and you shouldn’t have to know there are promises involved. Promises should let you write more concise and easier to read code that powers you higher level abstractions. And those abstractions can still use events because of all the reasons you mention at the end (decoupling, etc).

    I hope that makes some sense.

    • Jon

      Juan, thanks for enlightening me, I’d been coding interactions with promises using then(). So you don’t return the actual promise? Could you give an example of how you’d write the find method (for example) to return an object with the item method when the promise is resolved?

      • http://twitter.com/juandopazo Juan Ignacio

        The item() method should be provided by your library. Or you could add it somewhere by monkey-patching.

        Obviously that example doesn’t make any sense in real life. A good example of a library that provides a chainable API that looks like your example is the MongoJS wrapper for MongoDB on Node (https://github.com/gett/mongojs):


        // from the MongoJS docs
        db.mycollection.find({}).limit(2).skip(1).forEach(function(err, docs) {
        //...
        });

        Actually MongoJS doesn’t use promises. And that’s my point. The MongoJS API is easy to write using promises, but you as a user don’t really care about that, don’t you?

    • http://www.joezimjs.com Joe Zimmerman

      Yes and no. If it’s worth your time to hide the promises behind your chainable interface then it is probably ideal. I fully agree with you except:

      1) As you can see in your example, since `login` and `query` have nothing to do with each other, there’s no to chain them together. If there was another async operation that had nothing to do with `query` then you’d need to set up another level of callbacks. So, you’re above example only eliminates all but one callback for each chainable set of commands.

      2) A Promise is a known interface that is used in a lot of places. Many developers already know how to use them, so that means that you’re helping your user understand how to use your library without needing to teach them more about your API.

      So we could definitely just change your example in a way that `login` and `query…save` can accept the callback as a final argument, but it will also return a promise so that people can avoid this nesting situation.

      • http://twitter.com/juandopazo Juan Ignacio

        So we could definitely just change your example in a way that `login` and `query…save` can accept the callback as a final argument, but it will also return a promise so that people can avoid this nesting situation

        The only reasons I’d return a promise at the end of the chain would be that there could be more operations to be done, or that the chain represents a value that can be consumed by another promise based operation. For example,if you’re retrieving a value from a DB or using ajax, then I’d provide a simple callback and also return a promise so that you can pass that promise around representing its value.

        My point was in the context of this post. There’s a lot of people asking themselves if they should use callbacks, promises or events, and many others promoting promises as a way of avoiding indentation. My answer to those is: promises should be there not to reduce indentation by chaining then(), but to help writing fluent APIs and pass around asynchronous values.

        • http://www.joezimjs.com Joe Zimmerman

          And that’s a very good point. Thank you for bringing it to light. For me, I complain and push for reducing the indentation in order to add clarity in the events that take place, not so much just for the sake of reducing indentation.

          But as you said, that’s not the main point of promises. Anyway, I still maintain that there is no problem with returning promises to users from your own API. I agree that – if possible – making your API fluent without the need to return the promises is ideal, but there are situations where giving your users the promises is a good idea, like what you stated with the ajax/db example.

          So yea… no real disagreements here then. =)

  • Jeff Parrish

    I think promises are sometimes useful, but the issue of “nested” callbacks (christmas tree syndrome) can be trivially solved by just passing a named function as an argument instead of anonymous functions:

    asyncCall( param1, param2, HandlerCallback );

    function HandlerCallback(err, res){
    // do stuff
    }

    • http://www.joezimjs.com Joe Zimmerman

      True, but if the function is built entirely for the purpose of handling this single operation, then if you end up 5 levels deep, you’ll have 5 named functions that aren’t reusable simply for the sake of reducing indentation. This isn’t “wrong” or “bad”, but then again, nested anonymous callbacks aren’t either.

      If you’re using reusable named functions for this then YES, this is the RIGHT way to do it. Either way, promises should work for you.

      Then we have to look at dependency. In your example you show only a single callback. Let’s expand it to be 2 levels deep:

      async1(param, handler1);

      function handler1(err, etc) {
      // do stuff

      async2(param, handler2);

      }

      function handler2 (err, etc) {

      // do stuff

      }

      This works perfectly fine, but now handler1 is dependent on handler2. Let’s convert this to promises:

      async1(param).then(handler1).then(handler2);

      function handler1(err, etc) {
      // do stuff
      return async2(param);
      }

      function handler2 (err, etc) {
      // do stuff
      }

      Now handler1 knows nothing about handler2 AND it’s more obvious what is happening because you can see that we use both handler1 AND handler2 on the first line.

      I wish I had thought of this earlier and mentioned it in the article.

      • http://www.arrogantgamer.com/ arrogant.gamer

        “nested anonymous callbacks aren’t either”

        This isn’t always true? If you are creating a lot of anonymous functions (for example, if you create an anonymous function in a loop), the garbage collection and memory consumption isn’t completely trivial.

        I use anonymous functions in the way you prescribe, for sure, but I also tend to have lots of functions that do just one thing or aren’t reusable to avoid the Christmas tree thing.

        This is kind of off-topic though.

    • Phillip Senn

      I think I’m starting to realize the same thing.

  • http://domenicdenicola.com/ Domenic Denicola

    It might be worth linking to an actual promise implementation instead of jQuery’s broken one. For more detail see http://domenic.me/2012/10/14/youre-missing-the-point-of-promises/

    You can see a list of non-broken implementations here: https://github.com/promises-aplus/promises-spec/blob/master/implementations.md

    • http://www.joezimjs.com Joe Zimmerman

      Did you actually test what the author of that article wrote? According to the JSFiddle that I wrote, jQuery 1.9.1’s promises work fine: http://jsfiddle.net/joezimjs/uH5pD/

      • http://domenicdenicola.com/ Domenic Denicola

        No, they don’t. See the section starting “This breaks down into four scenarios, depending on the state of the promise. Here we give their synchronous parallels so you can see why it’s crucially important to have semantics for all four:”

        • http://www.joezimjs.com Joe Zimmerman

          Sooo, which scenario does jQuery break on? Show me an example using both jQuery and another library that demonstrates where jQuery doesn’t work.

          • http://domenicdenicola.com/ Domenic Denicola

            Scenarios 2 through 4, as stated in that section. Here is a demo of jQuery failing on scenario 2 and 3 and Q succeeding:

            http://jsfiddle.net/6mEMu/

            You can easily construct others for scenario 4, or other combinations.

            • http://www.joezimjs.com Joe Zimmerman

              Thank you for demonstrating that. I was having difficulty understanding what that author meant, and from what I read in another post that referenced that post made it sound like the example that I gave was the problem. I’ll update my post some time.

              • http://domenicdenicola.com/ Domenic Denicola

                Awesome, glad to help!

                Plot twist: I am the author :O

                • http://www.joezimjs.com Joe Zimmerman

                  That would explain why it was so easy for you to understand the article =)

  • http://twitter.com/adamlbarrett Adam L Barrett

    I think using the example in the post an easy demonstration of the value of promises would be if press of the imaginary “Start Server Button” had to start not only a web-server but also a database server and then update the UI only when they *both* were running.

    Using the .when method of promises would make this “multiple-asynchronous-operations” example trivial, whereas reacting to multiple asynchronous events requires a non-trivial amount of code.

    Then, of course, my example is strengthened when you add a third or fourth asynchronous operatin that must complete before you update the ui.

    Side note: I agree with Domenic about jQuery’s implementation, there are better examples.

    • http://www.joezimjs.com Joe Zimmerman

      I don’t think promises are bad and I actually use them around the application. I’m just trying to let people know that promises aren’t the only option. Event-based architecture is a viable and highly decoupled way of doing things.

  • http://killdream.github.com/ Quildreen Motta

    I think you’re missing the point of Promises. Promises are not a replacement for events, and they don’t even deal with the same problems. A Promise is a better way to handle the following scenario:

    “I want to find an user in this database, but the `find` method is asynchronous.”

    So, here we have a `find` method which doesn’t immediately return a value. But it does eventually “return” a value (by the means of a callback), and you want to handle that value in some way. Now, by using callback you can define a continuation, or “some code that will deal with that value at a later time.”

    Promises change that “hey, here’s some code to deal with the value you’ll find”. They are something that allow the “find” method to say “hey, I’ll be busy finding you the information you asked for, but on the mean time you can hang on this Thing that represents that value, and you can handle it in any way you want in the mean time, just like the actual thing!”

    Promises represent *real* values. That’s the catch. And they work when you can deal with the Promises in the same way you’d do with the actual thing. That Promise implementations in JavaScript expect you to pass a callback function to it is just an “accident”, it’s not the important thing.

    So, let’s say the “find” method takes a name, and returns you a list of people that match that name. And then you want to return the address of every one of those people, and display it on the screen. With promises, you just manipulate those values as if they’re available right away:


    function address(user){ return user.address }
    function concat_address(a1, a2){ return a1 + 'n---n' + a2 }

    display(preduce(concat_address, pmap(address, find('Bob'))))

    See how you can have your functions manipulate actual data? Address and Concat_Address are totally unaware that the data they’re dealing with is returned asynchronous. They’re pure, simple and beautiful. In that example, `pmap` is a variant of Array.prototype.map that takes in a Promise that represents an array, and returns a new Promise that represents that array mapped through some function. `preduce` does the same thing, but for Array.prototype.reduce. And `display` is something that takes a Promise of a String, and just writes it out to the screen when it’s done, thus `display` doesn’t return a Promise.

    A simple implementation of that could be (this uses my Cassie library, which is actually an evented promise implementation):


    function pmap(f, pxs) {
    var p = Promise.make()
    pxs.ok(function(xs){ p.bind(xs.map(f)) })
    pxs.failed(function(err){ p.fail(err) })
    return p
    }

    function preduce(f, pxs) {
    var p = Promise.make()
    pxs.ok(function(xs){ p.bind(xs.reduce(f)) })
    pxs.failed(function(err){ p.fail(err) })
    return p
    }

    function display(pstring) {
    pstring.ok(function(string){ console.log(string) })
    pstring.failed(function(err){ console.error(err) })
    }

    Also, I think my examples outline it well, but I’d just like to make it clear that jQuery’s implementation of Promises isn’t an actual implementation of Promises, but of Deferreds, which are just callback aggregators that you can use instead of a single callback.

    • http://www.joezimjs.com Joe Zimmerman

      Very good and interesting point.

      I find it interesting how many times I hear “you’re missing the point of promises” and each person goes on to explain a different point of promises and each point is valid. This seems to tell me how amazingly useful promises are.

  • http://twitter.com/Path2SharePoint Christophe

    Sorry, but I don’t see any chaining in your second example (which is kind of the point of promises). How would you for example handle: “stop server 1 and 2, but only after server 3 and server 4 have started”?

    • http://www.joezimjs.com Joe Zimmerman

      How is doThis(…).then(…).then(…).then(…) not chaining?

      Now, are you asking me to show how to start/stop servers 1-4 with promises or events?

      • http://twitter.com/Path2SharePoint Christophe

        Correct, “then” is chaining. I don’t see any “then” in your server example.
        What I am asking is how you would do this with events: “start servers 3 and 4, then (after getting confirmation that 3 and 4 have started) stop servers 1 and 2″

        • http://www.joezimjs.com Joe Zimmerman

          Ah, I see. Well, first off, the example where I chained `then` calls was just to show people who have never seen promises one of the benefits of promises and how to use them.

          Second, in the application where I’m using the events, there is no reason to need to do anything like that whatsoever.

          Third, there is no simple way to do that with events. And I won’t even bother creating an example to show how someone might want to. Mostly because this article is about raising awareness for alternatives. Events aren’t a one-size fits all alternative. Each approach is more or less useful than another approach in certain situations and it is the developer’s job to decide which approach fits the situation.

          If there were a need to do something like you asked, then I would CERTAINLY use promises. I am already working through situations where both approaches fit in well and can be used together to make things even better.

          • http://twitter.com/Path2SharePoint Christophe

            Absolutely, they address different needs. I guess what confuses me is the “vs.” in your title, while your article really features two completely different asynchronous scenarios that cannot be compared. So maybe events make an “awesome difference” in the second scenario, but promises make an “awesome difference” in the first one (chaining).

            • http://www.joezimjs.com Joe Zimmerman

              I agree. Sometimes keeping myself to a schedule ends up with problems like this. Content doesn’t necessarily reflect the title and the content isn’t really consistent. I apologize for the sub-par article.