3

In Vapor 4, I'm processing a post request by calling a request on a 3rd party API and returning a value based on the result I get back. The following code results in the error: "Invalid conversion from throwing function ... to non-throwing function"

 app.post("activate") { req -> EventLoopFuture<ActivationRequestResponse> in

        return req.client.post("https://api.example.com/activation", headers: HTTPHeaders(), beforeSend: { (req) in
            try req.content.encode(RequestBody(value: someValue), as: .json)
        })

        .map { (response) -> ActivationRequestResponse in

            let response = try response.content.decode(ResponseModel.self)
            return ActivationRequestResponse(success: true, message: "success")

        }

    }

I can't seem to use try in my chained map() after getting the API result. The above code will work if I add a ! to the try in let response = try response.content.decode(ResponseModel.self) inside the map, but ideally I want to catch this error. The first try used when creating the response body seems to be implicitly passed back up the chain, but not the second.

What am I doing wrong? How do I catch the error when decoding the response content? Why is the first try caught but not the second?

GingerBreadMane
  • 2,630
  • 1
  • 30
  • 29

2 Answers2

3

The property of map is that it will just transform a value on the “success path”. Your transformation may however fail which means that you presumably want the future to fail too.

Whenever you want to transform a value with a function that either succeeds or fails you need to use one of the flatMap* functions.

In your case, try replacing map with flatMapThrowing and then it should work.

Johannes Weiss
  • 52,533
  • 16
  • 102
  • 136
  • 3
    It looks like flatMapThrowing can only be used to return a non-future. Suppose I need to return a future with another network call. Is there are way to transform a value that either succeeds or fails and then return a future? – GingerBreadMane Mar 09 '20 at 04:10
3

To expand on Johannes Weiss' answer, to have a throwing closure that returns a future, you need something like:

future.flatMap {
    do {
        return try liveDangerously()
    } catch {
        future.eventLoop.makeFailedFuture(error)
    }
}

After doing this too many times, I decided to roll my own (though the name is a bit dubious):

extension EventLoopFuture {
    @inlinable
    public func flatterMapThrowing<NewValue>(file: StaticString = #file,
            line: UInt = #line,
            _ callback: @escaping (Value) throws -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
        return self.flatMap(file: file, line: line) { (value: Value) -> EventLoopFuture<NewValue> in
            do {
                return try callback(value)
            } catch {
                return self.eventLoop.makeFailedFuture(error)
            }
        }
    }
}

That way you can just write:

future.flatterMapThrowing {
    return try liveDangerously()
}
microtherion
  • 3,938
  • 1
  • 15
  • 18