Skip to content

Async

You may have noticed some APIs in Vapor expect or return a generic Future type. If this is your first time hearing about futures, they might seem a little confusing at first. But don't worry, Vapor makes them easy to use.

This guide will give you a quick introduction to working with Async. Check out Async → Overview for more information.

Futures

Since Futures work asynchronously, we must use closures to interact with and transform their values. Just like optionals in Swift, futures can be mapped and flat-mapped.

Map

The .map(to:_:) method allows you to transform the future's value to another value. The closure provided will be called once the Future's data becomes available.

/// Assume we get a future string back from some API
let futureString: Future<String> = ...

/// Map the future string to an integer
let futureInt = futureString.map(to: Int.self) { string in
    print(string) // The actual String
    return Int(string) ?? 0
}

/// We now have a future integer
print(futureInt) // Future<Int>

Flat Map

The .flatMap(to:_:) method allows you to transform the future's value to another future value. It gets the name "flat" map because it is what allows you to avoid creating nested futures (e.g., Future<Future<T>>). In other words, it helps you keep your futures flat.

/// Assume we get a future string back from some API
let futureString: Future<String> = ...

/// Assume we have created an HTTP client
let client: Client = ... 

/// Flat-map the future string to a future response
let futureResponse = futureString.flatMap(to: Response.self) { string in
    return client.get(string) // Future<Response>
}

/// We now have a future response
print(futureResponse) // Future<Response>

Info

If we instead used .map(to:_:) in the above example, we would have ended up with a Future<Future<Response>>. Yikes!

Chaining

The great part about transformations on futures is that they can be chained. This allows you to express many conversions and subtasks easily.

Let's modify the examples from above to see how we can take advantage of chaining.

/// Assume we get a future string back from some API
let futureString: Future<String> = ...

/// Assume we have created an HTTP client
let client: Client = ... 

/// Transform the string to a url, then to a response
let futureResponse = futureString.map(to: URL.self) { string in
    guard let url = URL(string: string) else {
        throw Abort(.badRequest, reason: "Invalid URL string: \(string)")
    }
    return url
}.flatMap(to: Response.self) { url in
    return client.get(url)
}

print(futureResponse) // Future<Response>

After the initial call to map, there is a temporary Future<URL> created. This future is then immediately flat-mapped to a Future<Response>

Tip

You can throw errors inside of map and flat-map closures. This will result in the future failing with the error thrown.

Worker

You may see methods in Vapor that have an on: Worker parameter. These are usually methods that perform asynchronous work and require access to the EventLoop.

The most common Workers you will interact with in Vapor are:

  • Application
  • Request
  • Response
/// Assume we have a Request and some ViewRenderer
let req: Request = ...
let view: ViewRenderer = ...

/// Render the view, using the Request as a worker. 
/// This ensures the async work happens on the correct event loop.
///
/// This assumes the signature is:
/// func render(_: String, on: Worker)
view.render("home.html", on: req)