Chapter 3. Writing Asynchronous Code with Deferreds

Callbacks are a fundamental part of event-driven programming and are the way that the reactor indicates to an application that an event has arrived. As event-driven programs grow, handling both the success and error cases for the events in one’s application becomes increasingly complex. Failing to register an appropriate callback can leave a program blocking on event processing that will never happen, and errors might have to propagate up a chain of callbacks from the networking stack through the layers of an application.

Twisted provides an elegant abstraction called a Deferred to manage these callbacks. This chapter will give you practice using Deferreds by themselves and then demonstrate their real-world utility by integrating Deferreds into some client and server examples.

We’ll use Deferreds while writing asynchronous servers and clients throughout the remainder of this book.

It’s worth heading off a common misconception up front:

Practice will help you develop an intuition for how to structure asynchronous code. Let’s look at a Deferred so you can start getting some of that practice.

Deferreds have a pair of callback chains, one for success (callbacks) and one for errors (errbacks). Deferreds start out with two empty chains. You add pairs of callbacks and errbacks to the Deferred to handle successes and failures at each point in the event processing. When an asynchronous result arrives, the Deferred is “fired” and the appropriate callbacks or errbacks are invoked in the order in which they were added to the chains. Figure 3-1 diagrams a Deferred and its callback chains.

To get a feel for how Deferreds work, we can create them, register callbacks and errbacks with them, and fire them without involving the reactor.

Example 3-1 creates a Deferred d and uses its addCallback method to register the function myCallback with the callback chain. d.callback “fires” d and invokes the callback chain, which only contains myCallback. The argument passed to callback is propagated as an argument to the first function in the callback chain.

Running Example 3-1 produces the following:

    Triggering callback.

Example 3-2 creates a Deferred d and uses its addErrback method to register the function myErrback with the errback chain. d.errback “fires” d and invokes the first function in the errback chain, which only contains myErrback. The argument passed to errback is wrapped in a Failure object before getting passed to the errback function.

A Failure is Twisted’s implementation of a dressed-up Exception suitable for asynchronous communication. It contains a stack trace for where an asynchronous error actually happened (which might not be the current stack trace).

Running Example 3-2 produces the following:

    [Failure instance: Traceback (failure with no frames):
    <class 'twisted.python.failure.DefaultException'>:
    Triggering errback.
    ]

An asynchronous event may have many processing steps, each requiring a level of callbacks and errbacks. For example, a web request might need to be deserialized, formatted, and then cause a database insert, and each of those steps might possibly fail. Deferreds make it easy to manage these multiple levels of success and error handling in one place.

To register multiple levels of callbacks and errbacks with a Deferred, simply attach them to their callback chains in the order you want them invoked using addCallback and addErrback, as illustrated in Example 3-3. The result returned by a callback or errback in a Deferred chain is passed as the first argument to the next callback or errback in the chain.

Running Example 3-3 produces:

    <i><b>Hello World</b></i>

Note that registering a callback with addCallback also registers a “pass-through” for that level of the errback chain. Similarly, registering an errback with addErrback also registers a “pass-through” for that level of the callback chain. The chains always have the same length.

Deferreds also sport an addCallbacks method, which registers both a callback and an errback at the same level in their respective callback chains. For example:

d = Deferred()
d.addCallbacks(myCallback, myErrback)
d.callback("Triggering callback.")

Now that we have experience playing with callbacks and errbacks outside the reactor, let’s use them inside the reactor.

Example 3-4 retrieves a headline and then processes it, either converting it to HTML and then printing it or printing an error to stderr if the headline is too long.

Running Example 3-4 produces:

    <h1>Breaking News: Twisted Takes Us to the Moon!</h1>

Because the provided headline is fewer than 50 characters long, HeadlineRetriever fires the callback chain, invoking _toHTML and then printData, which prints the HTML headline.

Example 3-4 uses a helpful reactor method called callLater, which you can use to schedule events. In this example, we use callLater in getHeadline to fake an asynchronous event arriving after one second.

What happens when we replace the three lines before reactor.run() with the following?

    h = HeadlineRetriever()
    d = h.getHeadline("1234567890"*6)
    d.addCallbacks(printData, printError)

Running this version of the example, we get:

    [Failure instance: Traceback (failure with no frames):
    <class 'twisted.python.failure.DefaultException'>:
    The headline ``1234567890123456789<...>01234567890'' is too long!
    ]

In this version, HeadlineRetriever encounters a headline that is too long and fires the errback chain: a pass-through (from the call to addCallback(self._toHTML)), then printError. Figure 3-2 traces the path followed through the Deferred.

In this section, we’ll look at a series of examples where the functions from Example 3-5 are chained together in various ways as callbacks and errbacks in a Deferred that is then fired. For each example, think about what sequence of callbacks and errbacks is executed and what the resulting output is. In examples where the output includes a traceback, the middle of the traceback has been elided for brevity and clarity.

Now that you have some Deferred practice under your belt, a somewhat subtle point needs to be made: addCallbacks is not the same as sequential calls to addCallback and addErrback.

What’s the difference?

The salient difference is that callbacks and errbacks registered together using addCallbacks do not interact. Put another way, when a callback and an errback are registered together using addCallbacks, that errback can’t handle exceptions raised by that callback: exceptions raised at level N in the callback chain are processed by the errback at level N + 1.

Figures 3-3 and 3-4 depict the difference between a call to addCallbacks and sequential calls to addCallback and addErrback.

Given this distinction, what do the following Deferred chains do?

This section reiterates some important points about Deferreds and introduces a few new ones:

The Deferred API has one last method for adding callbacks, addBoth, which adds the same callback to both the callback and errback chains for the Deferred. Note that while we haven’t been passing arguments to our callback yet, that is supported by the API. The supported methods are:

This chapter introduced the Deferred, an abstraction that simplifies and centralizes the management of callbacks for success and error handling in your asynchronous programs.

We’ll use Deferreds while writing HTTP servers and clients in the next two chapters.

The Twisted Core HOWTO has two main documents on Deferreds, an overview of using them, and a guide to writing functions that generate them.