Categories
Programming Swift

map() versus flatMapThrowing()

This threw me off for a little and I’d like to share my findings.

I’m using Vapor on a little side-project and part of the beauty of that backend framework is that everything is a stream, backed by SwiftNIO. Now I haven’t done any benchmarks, performance-wise, but it’s nice to have a backend that uses streams, and a front-end built in a reactive way with Combine. It means the same paradigm applies to both and I don’t have to go all procedural in one and stream/reactive in the other.

Understandably, nothing is that easy. On the backend, we have EventLoopFuture which is a promise of some event taking place in the future. That’s the NIO way of doing streams. In SwiftUI, we have use publishers in Combine. Not exactly the same but close enough.

One thing I noticed is that SwiftNIO has a map function that has the following signature: map: (N -> T) -> EventLoopFuture<T> . That function is good for turning one type into another and continuing the stream. However, the transformation function (N -> T) might have a throwable exception (e.g. try model.requireID() ), so map will not work. I looked for what else might do the trick and stumbled on flatMapThrowing .

What is peculiar about flatMapThrowing is that it has a different signature from flatMap . The latter goes a little like this flatMap: (N -> EventLoopFuture<T>) -> EventLoopFuture<T> . It’s good for taking a value and returning a different stream, such as finding an object in the DB and using that for a separate query or async operation. flatMapThrowinghowever, has the following signature: flatMapThrowing: (N throwing -> T) -> EventLoopFuture<T> . Weird: where as flatMap requires the callback to return an EventLoopFuture , flatMapThrowing does not!

Looks like I’m not the only one with this confusion.

Anyway, hope it helps someone out there. A simple mapThrowing is simple enough (just rename the thing):

extension EventLoopFuture {
func mapThrowing<NewValue>(_ callback: @escaping (Value) throws -> NewValue) -> EventLoopFuture<NewValue> {
return flatMapThrowing(callback)
}
}

As always, once you learn something in our field, you remember it for good, so this wrapper might be unnecessary, but it took a minute to figure out.