util.async

This module provides an API to help implement asynchronous non-blocking code. It is divided into a handful of objects, each of which are described below.

runner

A runner essentially makes it possible to temporarily pause a function while it is executing, so that it can wait for the result of an external operation without blocking the server. Runners are implemented using Lua coroutines, but they should only be managed through util.async, the API documented here.

When creating a runner, you need to pass in a function that the runner will execute. This function will receive the data that you pass the runner using the :run() method (see below). Note that there is no direct mechanism for returning values from this function, all return values are ignored. However it's easy to simply share variables and such using usual Lua techniques.

runner:run(data)

Once a runner is created, you can run it as many times as you like. The given data will be passed to the function you provided, either immediately or when it has finished executing previous data if it is currently busy (each runner will only process one piece of data at a time).

runner:enqueue(data)

Add data to a runner's queue, without starting the runner if it is currently idle. You may call runner:run() later to start processing the queue.

waiter

A waiter can be used within a runner to temporarily suspend the runner until some event happens (typically a callback). The usage is simple.

When you call waiter(), it returns two functions. You must call the first one when you want to pause execution. Call the second one when you want to resume it again. An example explains best:

The wait() function must only be used once. To pause execution again, create two new functions by calling waiter().

As an additional feature, if you need to wait for multiple things to happen, you can pass a number to waiter(), and it will wait for done() to be called that number of times before resuming from wait(). For example:

Examples

Basic example

Here is a basic example, using net.adns, Prosody's asynchronous DNS lookup API:

At a first glance,it might not be obvious what benefit util.async offers here. Why not just use the callback directly?

Concurrent callbacks example

A good way to demonstrate how util.async makes life easier is to show what happens if we need to do two DNS lookups at the same time. This is a real example - we generally need to look up both A records (IPv4) and AAAA records (IPv6).

Here is what that code might look like using only callbacks, without util.async:

Note how, because we cannot tell which request will be answered first, we have to perform the completion check in both callbacks. Thankfully we only have two! Also, to avoid code duplication, we've managed to factor out the "all done" case to its own function, which is called from whichever callback happens second.

However using util.async, we can forget all this complex logic, and write the code step-by-step, the way we naturally would think about the task in our heads:

An improvement, no?

Sequential callbacks example

The improvement that util.async brings becomes even more striking if we consider a slightly different example. Here we're going to do three things, one after the other. First, we'll wait 5 seconds, then perform a DNS lookup, and then make a HTTP request:

This code demonstrates the problem with sequential operations in callback-based APIs, often referred to as the 'Pyramid of Doom'. And our example is only quite a simple one with little actual logic.

Let's see what happens if we rewrite the code using util.async, which allows us to flatten it out using waiters: