One of the frustrating differences I ran into is the callback mechanism in tornado. You can't attach more than one callback, and the one kind of callback does two different things (meaning the callback has to determine whether or not the call was successful before it does its work).

To me, this makes things more complicated. What if I want to use the same error handling code for all bits of code (really common for me is `d.addErrback(log.err)` to just log it), but each successful call has to do something different?

Twisted deferreds also allow you to compose chains.

This is a somewhat trivial example, but by having the callback do both success and error cases, I had to build an adaptor to pass to callbacks to include the "is this a failure" information.

The easy way to do that is to create a wrapper class and construct it one way or another based on whether a success or error occurred. I ended up just composing it like this:

deferred.addCallback(lambda x: Response(body=x))
deferred.addErrback(lambda e: Response(error=e))
deferred.addCallback(lambda r: callback(r, *args, **kwargs))

The first callback and errback create a Response (wrapper) object that contains either a body or an error. The second callback receives the response that was made by one of the above and calls the initial callback.

This is sort of unrolling complexity and sweeping it away. The real problem is that you can only have one callback in tornado (and you can't easily chain them). That means that what actually happens when this deferred's callback fires is that it ends up calling the original callback, which is an inner wrapper function inside of another function that calls a curried function that calls your original callback -- and you have to create these things by name when you're building out your auth class.

To me, that's complicated.

deferreds seem like there's more involved than you get with a simple callback until you actually try to use the simple callback.

The interesting properties of deferreds are

1) You can add multiple callbacks and errbacks and build chains as complicated as your system needs including composing common cases (such as success and error counters).

2) If a callback (or errback) returns a deferred, you spin off another dimension of the chain.

#2 is why the above code and about twenty lines of web.py exist for a single use case.

Here's a concrete example (close to something I do in the real world™):

Imagine a simple twitter API. You go grab a tweet and then give it to something else: `t.grabTweet(something).addCallback(processTweet)`

Now someone adds a requirement that between `grabTweet` and `processTweet` you want to perform short URL expansion. In twisted, I'd just toss an `addCallback(expandURL)` in the middle and call my awesomely well-tested URL expander -- which makes an async http call to another service and doesn't call the next callback in the chain until that service returns the data I need to fill satisfy the request.

And then after I did that, I found that the URL expansion stuff I was using was blowing up the upstream and causing me to fail to expand some, so I added async cache support to those inside the API (not as an additional toplevel callback). For this new step, the toplevel API doesn't change, but now I can do more async stuff that may lead to further async stuff without having to write additional glue code.