map and flatMap in Vapor

Jonathan Wong
4 min readJan 9, 2019

If you’re getting started with Vapor, a common question is, “when do you use map vs flatMap?” In this post, we’ll take a quick look at some examples to help clear it up.

Why map and flatMap?

First of all, why do you need map and flatMap to begin with? Some of Vapor APIs return a Future<SomeType>. You can’t work with SomeType on its own until you unwrap it. map and flatMap both allow you to unwrap SomeType.

They map a Future to a Future of a different type. From Vapor’s documentation, map is to be used when the result returned within should be something that’s not a Future type. Use flatMap when the result returned within the closure should be another Future. Using flatMap this way lets you avoid creating nested Futures. This concept is similar in plain old Swift where flatMap can reduce a level of nesting.

let arrays = [["a", "b"], ["c", "d"]]
let flattenedArray = arrays.flatMap { $0 }
// ["a", "b", "c", "d"]

Let’s use that knowledge and look at a few examples.

Example 1

router.post("v1", "users") { req -> Future<User> in 
return try req.content.decode(User.self)
.flatMap(to: User.self) { user in
return user.save(on: req)
}
}

The above code shows a POST request on the route v1/users . It takes in a User object, saves it, and then returns that Future<User>. Why do we use flatMap here instead of map?

If we look at the method signature of save, which is the method that returns our result in the closure, we see that it returns a Future<User> . Because this is of a Future type, we use flatMap. (Note: If you dive deeper into the source code of Vapor, you’ll see EventLoopFuture. EventLoopFuture is part of SwiftNIO and Vapor defines Future as a typealias for EventLoopFuture. Since this article is about Vapor, I’ll stick with the Vapor nomenclature of Future.)

Example 2

router.post("v1", "users") { req -> Future<HTTPStatus> in
return try req.content.decode(User.self)
.flatMap(to: HTTPStatus.self) { user in
return user.save(on: req)
.map(to: HTTPStatus.self) { user in
return HTTPStatus.noContent
}
}
}

In this example, we also take a User object in a POST request but instead of returning the user we just saved, we return a Future<HTTPStatus>. (Note: This probably isn’t something you’re going to want to do in an app, but it shows how to use flatMap and map in one function.) Once we decode our User, similar to the first example, since in our first closure we call save which returns a Future type, we need to use flatMap. Once we have that Future<User>, we want to return a type of Future<HTTPStatus> to match the return type of our router. Now because our second closure returns a type that is not a Future, we go ahead and use map here.

Example 3

func sampleEdit(_ req: Request) throws -> Future<Response> {
let sample = try req.parameters.next(Sample.self)
let value = try req.parameters.next(String.self)
return sample.flatMap { updatedSample in
if value.lowercased() == "true" {
updatedSample.isProcessed = true
} else if value.lowercased() == "false" {
updatedSample.isProcessed = false
}
return updatedSample.save(on: req)
.map(to: Response.self) { savedSample in
guard savedSample.id != nil else {
throw Abort(.internalServerError)
}
return req.redirect(to: "/users/\(savedSample.user.parentID)")
}
}
}

This function is used to update a Sample object on a route that looks like samples/Sample.parameter/String.parameter. Basically, the id of the Sample to update is passed in, along with the String value true or false that then updates a Bool flag on the Sample model. If we look at where flatMap is called, the first closure that uses updatedSample calls save which as you remember returns a Future type. Once we have our Future<User> , we want to return a Future<Response>. Our app uses the redirect method which does not return a Future type, so we use map here.

Example 4

import Asynclet loop = EmbeddedEventLoop()
let promise = loop.newPromise(String.self)
let futureString: Future<String> = promise.futureResult
let futureInt = futureString.map(to: Int.self) { string in
print("string: \(string)")
return Int(string) ?? 0
}
promise.succeed(result: "15")
print(try futureInt.wait())

This example is modeled after Vapor’s great documentation. If you haven’t checked it out, definitely give it a look. You’ll notice here we are just importing Async. This is a nice way to play around with Futures and Promises without having to build a bigger application.

In this example, we use an event loop to create a new promise. This allows us to create a Future<String>. We can use map here to map this Future<String> to a Future<Int>. The result returned within the closure here is not another Future, so we can use map.

We have the Promise succeed with a value of "15" and then call wait() on the futureInt to get the Int value 15.

Conclusion

You looked at four different examples of map and flatMap to hopefully clear up some of the confusion around using them when using Vapor. If you liked this, check out my course Getting Started with Server-side Swift and Vapor on Pluralsight. Some of the examples were lifted from the sample app we build from scratch in that course ;-).

--

--

Jonathan Wong

Cook, eat, run… San Diego Software Engineer, Pluralsight Author, RW Team Member